How to read .ai (Adobe Illustrator) file w/o Illustrator

Read .ai files with Ghostscript and XnViewIf you ran to .ai file which is Adobe Illustrator file, and you do not have Illustrator installed anywhere near, try this:

  1. Download and install latest Ghostscript package
  2. Download and install latest XnView
  3. Use XnView to open .ai file and to save it as other (more common) image format

That should do the trick (it did for me).

VPN, Remote Desktop, Vista and MTU

LinkSys WAG325NMy home network is rather small: two desktop machines and one laptop. Access to outside world is done via usual ADSL connection. Bridge between two worlds was some unknown ADSL router connected to (good old) LinkSys WRT54G with custom DD WRT firmware; all work was done here: firewall, port forwarding, dhcp …

This combination was good, but not the best – main obstacles were having two devices instead of one (WRT54G has no ADSL port) and no VPN directly to offices where I work.

Recently, I replaced WRT54G with newer model: LinkSys WAG325N: ADSL port, 4 LAN ports, 802.11 a/b/g wireless and experimental (draft) implementation of 802.11n wireless protocol. Best of all, up to 5 VPN channels directly from router, so all machines can use VPN tunnel w/o problems.

After seting up VPN, access to business machines was just fine from desktop machines. However, that was not the case for laptop, which is using wireless connection.

Using Remote Desktop from laptop, I would see initial black or blue screen of remote computer (w/o login box) and after couple of minutes, Remote Desktop session would die.

After considerable time spend on Google (and with great help of our system administrator Nemanja), I found possible problem: size of MTU or Maximum Transmition Unit – size of the largest packed allowed on particular network.

Default size set by Windows Vista was 1500 and that was enought not to establish Remote Desktop connection over VPN. In Vista, you can check settings by going in Command Prompt and typing:

netsh interface ipv4 show subinterfaces

and you will get something like:


  MTU  MediaSenseState   Bytes In  Bytes Out  Interface
------  ---------------  ---------  ---------  -------------
4294967295          1          0       7127  Loopback Pseudo-Interface 1
1500                1     145661      51022  Wireless Network Connection
1500                5          0          0  Local Area Connection
1500                5          0          0  Bluetooth Network Connection

Now, you can change MTU for particular interface:

netsh interface ipv4 set subinterface "Wireless Network Connection" mtu=1440 store=persistent

(make sure that you are doing this from elevated Command Prompt, i.e. CMD started using right-click and option “Run as Administrator“) and that will sort out any Remote Desktop connection problems.

Upgrade of Application/User Settings between application versions

One of the polished areas of .Net Framework 2.0 is manipulation with Application/User settings in WinForms applications. You can store almost anything in appropriate app.config or user.config file, and to manipulate with those settings with ease – wrapper class created around Settings allows you to use settings as just another field in your code.

But, what about of upgrade existing settings once when you create new version of the application?

That is supported via Upgrade() method:


	 Settings.Default.Upgrade();
	 Settings.Default.Save();

You can add one user bool setting (for example UpgradeSettings) with default value True, which will be a flag if upgrade is carried out; code can be:


	if (Settings.Default.UpgradeSettings)
	{
		 Settings.Default.Upgrade();
		 Settings.Default.UpgradeSettings = false;
		 Settings.Default.Save();
	}

Suppose that your application is simple little application, not a Click-Once or even distributed with installer – just archive which users can download, unpack and start using.

If you new version picks user settings folder from previous one, all is fine – Upgrade() method will find previous version and upgrade them:

Application versions - as expected

Suppose that this is true:

  • both versions are either signed or not (if you do signing of assembly between versions, Upgrade will fail)
  • both versions are either Click-Once applications or not (for the same reasons)

In theory, all is fine and Upgrade should do the job. But, what if all above is fulfilled and that you still have different user settings folder:

Application versions - mismatch

What can be reason that same application creates completely different user settings folder? This situation happened to me. After couple of hours, I could not determine reason, so I pulled out heavy tools – Reflector.

If you start tracking .Upgrade(), it goes to: ApplicationSettingsBase.Upgrade which simply calls IApplicationSettingsProvider.Upgrade for each provider present. For simple WinForms application, appropriate provider is LocalFileSettingsProvider. After some more thorough analysis, internal method for determining folder of settings store is:

System.Configuration.ClientConfigPaths

One of elements in determining local store for user settings was hashed value derived from Evidences for given assembly.

It turns out that one of Evidence elements for non-signed assemblies is path from where assembly/application is launched!.

To summarize: in order for Upgrade() of settings succeed, your new version of application should be deployed in the exact same folder where previous version is found; otherwise, Upgrade() will fail (or at least, it won’t do what you expected 🙂 )

Breaking changes for language codes in KB928365, KB928366

.Net FrameworkSome security updates are not just security updates.

If you installed (or you have Automatic Update turned on) yesterday’s updates:

  • KB928365 – Security update for the .NET Framework 2.0 for Windows Server 2003, Windows XP, and Windows 2000
  • KB928366 – Security update for the .NET Framework 1.1 for Windows XP and Windows 2000

you will get security update (nice) and breaking changes (not so nice) regarding some of the languages in the framework. More precise, some of specific cultures changed their codes:

LCID Old code New code Old / New description
2074 sr-SP-Latn sr-Latn-CS Serbian (Latin, Serbia and Montenegro) /
Serbian (Latin, Serbia)
3098 sr-SP-Cyrl sr-Cyrl-CS Serbian (Cyrillic, Serbia and Montenegro) /
Serbian (Cyrillic, Serbia)
1068 az-AZ-Latn az-Latn-AZ Azeri (Latin, Azerbaijan)
1091 uz-UZ-Latn uz-Latn-UZ Uzbek (Latin, Uzbekistan)
1025 div-MV dv-MV Divehi (Maldives)
2092 az-AZ-Cyrl az-Cyrl-AZ Azeri (Cyrillic, Azerbaijan)
2115 uz-UZ-Cyrl uz-Cyrl-UZ Uzbek (Cyrillic, Uzbekistan)
7194 sr-BA-Cyrl sr-Cyrl-BA Serbian (Cyrillic) (Bosnia and Herzegovina)
5146 bs-BA-Latn bs-Latn-BA Bosnian (Bosnia and Herzegovina)
6170 sr-BA-Latn sr-Latn-BA Serbian (Latin) (Bosnia and Herzegovina)
9225 en-CB en-029 English (Caribbean)

(this Caribbean change looks very suspicious, but code says so)

Those changes will cause you problems if you have satellite assemblies for given languages in your application – after client installs update(s), those translations will simply cease to work – recompile and distribution of new ones is mandatory.

I appreciate updates but some sort of warning or information on official patch pages would be, at least, nice.

How to render Html content into JPG/JPEG image in best quality (WinForms)

How to save Html to JPG/JPEG Image and how to save it in best quality?

On recent project, I had a tasks to:

  1. Save result of Html page to JPG image
  2. To do that in best possible quality

Both tasks sounds trivial, but I did not find quite satisfying solutions, so, I came up with one bellow.

First, you need WebBrowser control – it is basicaly wrapper around ActiveX Internet Explorer interface.

Once when Html content is ready (local or remote), ask control to call your procedure upon completion:

webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);

and start render your content:

webBrowser1.Navigate( UriToRender )

In DocumentCompletedEventHandler, call your routine for rendering content to image:

void webBrowser1_DocumentCompleted(object sender, 
WebBrowserDocumentCompletedEventArgs e)
{
  if( GetImageFromHtml(currentFile, webBrowser1) )
  {
     toolStripStatusLabel1.Text = "Image saved.";
  }
  else
  {
     toolStripStatusLabel1.Text = "Sorry, there was problem with saving.";
  }
}

And here is code for rendering Html to image:

/// <summary>
/// Method for saving Html content into image
/// </summary>
/// <param name="imageName">File name of image</param>
/// <param name="webBrowser">Target WebBrowser control to query for content</param>
/// <returns>True if save was succesfull</returns>
/// <seealso cref="System.Windows.Forms.WebBrowser" />

public bool GetImageFromHtml(string imageName, WebBrowser webBrowser)
{
  if (webBrowser.Document == null)
  {
     return false;
  }

  Cursor current = Cursor;
  Cursor = Cursors.WaitCursor;

  // Give time to WebBrowser control to finish rendering of document
  Thread.Sleep(200);

  try
  {

     // save old width / height
     Size originalSize = new Size(webBrowser1.Width, webBrowser1.Height);
     
     // Change to full scroll size
     int scrollHeight = webBrowser.Document.Body.ScrollRectangle.Height;
     int scrollWidth = webBrowser.Document.Body.ScrollRectangle.Width;
     
     Bitmap image = new Bitmap(scrollWidth, scrollHeight);
     webBrowser.Size = new Size(scrollWidth, scrollHeight);
     
     // Draw to image
     webBrowser.DrawToBitmap(image, webBrowser.ClientRectangle);
     webBrowser.Size = originalSize;

     // Old one with bad quality:
     // image.Save(imageName, ImageFormat.Jpeg);

     // Save in full quality
     SaveJPG(image, imageName, 100);

     return true;
  }

  catch { return false; }

  finally {  Cursor = current; }
}

Usual Image.Save method saves image in number of formats; however, quality is some default (bad) quality; and I needed more control over it. So, here are methods which allow that kind of control:

/// <summary>
/// Gets codec info by given mimeType
/// </summary>
/// <param name="mimeType">mimeType to lookup for</param>
/// <returns>ImageCodecInfo if all ok or null</returns>

public static ImageCodecInfo GetCodecInfo(String mimeType)
{
  ImageCodecInfo[] encoders;
  encoders = ImageCodecInfo.GetImageEncoders();
  for (int iterator = 0; iterator < encoders.Length; ++iterator)
  {
     if (encoders[iterator].MimeType == mimeType)
        return encoders[iterator];
  }
  return null;
}

/// <summary>
/// Save an Image to JPEG with given compression quality
/// </summary>
/// <param name="image">Image to save</param>
/// <param name="imageName">File name to store image</param>
/// <param name="quality">Quality parameter: 0 - lowest quality, smallest size, 
/// 100 - max quality and size</param>
/// <returns>True if save was succesfull</returns>

public static bool SaveJPG(Image image, string imageName, long qual)
{
  EncoderParameters eps = new EncoderParameters(1);
  eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qual);
  ImageCodecInfo ici = GetCodecInfo("image/jpeg");

  if(ici == null) { return false; }

  image.Save(imageName, ici, eps);
  return true;
} 

Now you can select jpeg image quality and render html the way you like.

Digg!

VS 2005 SP1 Installation Nightmare :-( … and how to wake up

Visual Studio 2005 SP1As you probably know, long awaited Visual Studio 2005 SP1 is out.

However, installation of this giant (400+ Mb) update is far from easy, and it is not guaranteed to succeed, at least at first run.

  1. You need to have a LOT free space on your system drive: at least 3 Gb
  2. Make sure that you have enough patience – it can take between 10 minutes and 1,5 hour
  3. If you do not use C++ portion of Visual Studio, deinstall it before installation of SP1 – it can save a lot of installation time.
  4. Deinstall Web Application Project if you have it installed.
  5. Deinstall Visual Studio 2005 Web Deployment Projects if you have it installed.
  6. Deinstall Visual Studio 2005 SP1 beta

Finally, you can speed up installation process by:

Disabling Patch Cache

Create CMD file with following content:
reg export HKLMSoftwarePoliciesMicrosoftWindowsInstaller installer.reg
reg add HKLMSoftwarePoliciesMicrosoftWindowsInstaller /v MaxPatchCacheSize /t REG_DWORD /d 0 /f
net stop msiserver
start /wait VS80sp1-KB926601-X86-ENU.exe
reg delete HKLMSoftwarePoliciesMicrosoftWindowsInstaller /v MaxPatchCacheSize /f
reg import installer.reg
net stop msiserver
del /q installer.reg 2>nul

and save it in folder where SP1 is. Run it – it should cut down installation time significantly.

Ok, you succeeded to do all above, and installation is failing. What next?

Error 1718

Let assume that your operating system is XP SP2. If you got during installation this error:

Error 1718. File was rejected by digital signature policy

try recipe from this location:

http://support.microsoft.com/kb/925336 – this helped with installation on two locations where it failed previously.

I have Windows Server 2003 SP1 and unfortunately, neither of solutions (disable patch cache or KB925336) helped 🙁

I though that memory upgrade will help, so I added 1Gb to total of 2Gb – no luck.

You can also try to:

Disable SAFER check for local administrators

– Note (if exist) previous value of PolicyScope at:

HKEY_LOCAL_MACHINESOFTWAREPoliciesMicrosoftwindowssafercodeidentifiers

– Set this value (or create it as DWORD if does not exist) to 1

– Run net stop msiserver

– Try SP1 install

– Revert PolicyScope to previous value / delete it after installation.

(this helped on two other Windows 2003 SP1 machines; again, not on mine)

Finally, I decided to:

Slipstream SP1 into Visual Studio 2005 and reinstall it

There are couple steps here:

Make Network Installation

Create network administrative installation from your VS 2005 DVD (assuming that E: is DVD and location where you want installation \dvesicpublicVisualStudio2005SP1):

msiexec.exe /a E:vsvs_setup.msi TARGETDIR=\dvesicpublicVisualStudio2005SP1 /L*vx install.log

I did this all on same machine, using mounted ISO image on a virtual drive and unpacking it at network location on same machine – it took over 2 hours to create network install

Unpack the Patch

Original EXE file is just wrapper around MS Patch file. You can unpack it using some unzip utility (or Total Commander) or by executing:

VS80sp1-KB926601-X86-ENU.exe /extract .

Patch Network Install

Finally, do the patching of installation:

msiexec.exe /a \dvesicpublicVisualStudio2005SP1vs_setup.msi /p VS80sp1-KB926601-X86-ENU.msp /L*vx patch.log

Reinstall Visual Studio 2005 SP1

I hope that at least some of this will help you too.

Reference links:

http://weblogs.asp.net/jgalloway/…/things-i-wish-i-d-known-before-i-installed-vs-2005-service-pack-1.aspx

http://blogs.msdn.com/heaths/…/save-time-and-space-for-vs-2005-sp1-by-disabling-the-patch-cache.aspx

http://blogs.msdn.com/heaths/…/slipstreaming-visual-studio-2005-service-pack-1.aspx

http://blogs.msdn.com/heaths/…/Enabling-Large-Patches-to-Install.aspx

Update 12th May 2007

There is update availabile which will hopefully sort this problems out on easy way.

Digg!

Visual Studio 2005 SP1 is out!

Visual Studio 2005 SP1
Visual Studio 2005 SP1 is finally released.

Apart from bugfixes and corrections, Service Pack 1 also provides over 70 improvements for common development scenarios including:

  • New processor support (e.g., Core Duo) for code generation and profiling
  • Performance and scale improvements in Team Foundation Server
  • Team Foundation Server integration with Excel 2007 and Project 2007
  • Tool support for occasionally connected devices and SQL Server Compact Edition
  • Additional support for project file based Web applications
  • Windows Embedded 6.0 platform and tools support

However, this is not final solutions for developers working on Vista operating system 🙁

More info here: http://msdn.microsoft.com/vstudio/support/vs2005sp1/

Download (of hefty 432 MB) is here:
http://www.microsoft.com/downloads/details.aspx?familyid=BB4A75AB-E2D4-4C96-B39D-37BAF6B5B1DC

When SQL Express 2005 SP1 Upgrade does not succeed …

I was trying to do upgrade of SQL Express 2005 to SP1 version on one of development machines.

Each time, attempt failed with not-so-helpful-message:

SQL Server Setup did not have the administrator permissions required to rename a file: C:Program FilesMicrosoft SQL ServerMSSQL.1MSSQLTemplate Datamssqlsystemresource1.ldf. To continue, verify that the file exists, and either grant administrator permissions to the account currently running Setup or log in with an administrator account. Then run SQL Server Setup again.

Quick Google did the trick: solution was described in KB 918693 (to be precise: workaround).

However, at first glance, I failed to execute workaround – there are couple of errors in mentioned article:

1. If typing SQLCMD do not connect to local instance of SQL 2005 Express, try with full name of instance:

SQLCMD COMPUTER_NAMESQLEXPRESS

2. “beautified” quotes in article’s code won’t exactly help; so, if you want quick copy / paste, use code from here:

EXEC sp_configure 'user instances enabled',0
GO
RECONFIGURE
GO

3. Step three, apart from quotes, has one column name wrong, so again, use this code:

SELECT owning_principal_name, instance_pipe_name 
FROM sys.dm_os_child_instances WHERE heart_beat = 'alive'
GO

4. And here is script for final step, after reinstallation:

EXEC sp_configure 'user instances enabled',1
GO
RECONFIGURE
GO

BTW, how can I report errors spotted in KB article to Microsoft? Anyone?

How to make Windows Form app truly Full Screen (and to hide Taskbar) in C#?

One of sound-like-simple questions is “how to make your application truly Full Screen” i.e. not showing Taskbar or anything like that.

Initial approach is obvious:


    targetForm.WindowState = FormWindowState.Maximized;
    targetForm.FormBorderStyle = FormBorderStyle.None;
    targetForm.TopMost = true;

Does it work? Well, sort of. If your Taskbar have default setting unchecked for “Keep the taskbar on top of other windows”, this will present your application in all it’s glory all over screen estate.

However, if the Taskbar is set to appear on top of all others, this won’t help – your application won’t cover it.

Let’s go further – next step is to use P/Invoke and to engage Win32 API services. There is easy way to hide particular window. So, find the Taskbar and hide it:


    private const int SW_HIDE = 0;
    private const int SW_SHOW = 1;

    [DllImport("user32.dll")]
    private static extern int FindWindow(string className, string windowText);
    [DllImport("user32.dll")]
    private static extern int ShowWindow(int hwnd, int command);

    int hWnd = FindWindow("Shell_TrayWnd", "");
    ShowWindow(hWnd, SW_HIDE);

    targetForm.WindowState = FormWindowState.Maximized;
    targetForm.FormBorderStyle = FormBorderStyle.None;
    targetForm.TopMost = true;

(you need to add using System.Runtime.InteropServices;)

Is this better? In theory yes – Taskbar is hidden, but your application still does not occupy whole screen – place where Taskbar was is not used.

Real and proven solution is to make request to WinAPI that your form take whole screen estate – Taskbar will hide itself in that case. Full information about that can be found in KB Article Q179363: How To Cover the Task Bar with a Window and here is the code:


/// <summary>
/// Selected Win AI Function Calls
/// </summary>

public class WinApi
{
    [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
    public static extern int GetSystemMetrics(int which);

    [DllImport("user32.dll")]
    public static extern void 
        SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,
                     int X, int Y, int width, int height, uint flags);        
    
    private const int SM_CXSCREEN = 0;
    private const int SM_CYSCREEN = 1;
    private static IntPtr HWND_TOP = IntPtr.Zero;
    private const int SWP_SHOWWINDOW = 64; // 0x0040
    
    public static int ScreenX
    {
        get { return GetSystemMetrics(SM_CXSCREEN);}
    }
    
    public static int ScreenY
    {
        get { return GetSystemMetrics(SM_CYSCREEN);}
    }
    
    public static void SetWinFullScreen(IntPtr hwnd)
    {
        SetWindowPos(hwnd, HWND_TOP, 0, 0, ScreenX, ScreenY, SWP_SHOWWINDOW);
    }
}

/// <summary>
/// Class used to preserve / restore state of the form
/// </summary>
public class FormState
{
    private FormWindowState winState;
    private FormBorderStyle brdStyle;
    private bool topMost;
    private Rectangle bounds;

    private bool IsMaximized = false;

    public void Maximize(Form targetForm)
    {
        if (!IsMaximized)
        {
            IsMaximized = true;
            Save(targetForm);
            targetForm.WindowState = FormWindowState.Maximized;
            targetForm.FormBorderStyle = FormBorderStyle.None;
            targetForm.TopMost = true;
            WinApi.SetWinFullScreen(targetForm.Handle);
        }
    }
    
    public void Save(Form targetForm)
    {
        winState = targetForm.WindowState;
        brdStyle = targetForm.FormBorderStyle;
        topMost = targetForm.TopMost;
        bounds = targetForm.Bounds;
    }

    public void Restore(Form targetForm)
    {
        targetForm.WindowState = winState;
        targetForm.FormBorderStyle = brdStyle;
        targetForm.TopMost = topMost;
        targetForm.Bounds = bounds;
        IsMaximized = false;
    }
}

Code for example application is here: MaxWinForm.zip

Error when executing .MSI (Installer) package

MSI Error due to insufficient read rightsWhen you are starting installation of some package via MSI, and if you ran into error like this:

Error reading from file _full_path_to_msi_package. Verify that the file exists and that you can access it.

check that Local System account has read access over containing folder and .msi file itself.

First phase of installation starts under your account, so it will start as expected, but in some moment, one phase is executed via Windows Installer service, which in turn works under Local System account.