Archive for the ‘ClickOnce Deployment’ Category

How do I programmatically find the deployed files for a VSTO Add-In?

July 11, 2010

You can use ClickOnce deployment to install Office Add-ins for Office 2007 and Office 2010. It is very similar to deploying a desktop application, but not identical. With all types of ClickOnce deployments, you may include resources that you need to access programmatically. Files with a file extension of .xml, .mdf, and .mdb are assumed to be data and are deployed by default to the ApplicationDeployment.CurrentDeployment.DataDirectory, but non-data files will be found in the folder with the deployed assemblies.

With a non-VSTO application, you can programmatically find the location of the deployment by either accessing System.Windows.Forms.Application.StartupPath or by checking the Location of the executing assembly. With a VSTO application, the location of the executing assembly does not match the location of the deployment files.

The dll for a VSTO add-in is copied from the deployment directory into a separate location, and it is loaded by the host application from there. I would guess that this happens whenever you run the host application (such as Outlook), and this is why the Office add-in can be uninstalled, reinstalled, updated, etc., without impacting the host application. The changes don’t take effect until the host application is closed and reopened. So when you retrieve the executing assembly’s location, it points to the dll in the run location, and you can’t use that to track down the deployment files.

So how do you find the location of the deployment? You have to examine the CodeBase property of the executing assembly. This comes across as a URI, so you have to retrieve the LocalPath from the URI. It also includes the file name of the main assembly, so you have to retrieve just the directory name specifically.

Here’s the code in VB:

'Get the assembly information
Dim assemblyInfo As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly()

'Location is where the assembly is run from 
Dim assemblyLocation As String = assemblyInfo.Location

'CodeBase is the location of the ClickOnce deployment files
Dim uriCodeBase As Uri = New Uri(assemblyInfo.CodeBase)
Dim ClickOnceLocation As String = Path.GetDirectoryName(uriCodeBase.LocalPath.ToString())

Here’s the code in C#:

//Get the assembly information
  System.Reflection.Assembly assemblyInfo = System.Reflection.Assembly.GetExecutingAssembly();
                    
  //Location is where the assembly is run from 
  string assemblyLocation = assemblyInfo.Location;

  //CodeBase is the location of the ClickOnce deployment files
  Uri uriCodeBase = new Uri(assemblyInfo.CodeBase);
  string ClickOnceLocation = Path.GetDirectoryName(uriCodeBase.LocalPath.ToString());

When you compare these values for a non-VSTO ClickOnce application, the directories are the same. When I run this code for my Outlook Add-In, I get these values:

assemblyLocation =

C:\Users\Robin\AppData\Local\assembly\dl3\VBJZ5WH8.6NJ\HRZA3JXN.LVG\b0520efe\3a5b99ef_2e21cb01\GoldMail Outlook Add-In.DLL

ClickOnceLocation =

C:\Users\Robin\AppData\Local\Apps\2.0\ZMBZ82EH.TDG\WHXVWE4L.GZ7\gold..vsto_7a251ffffc558391_0002.0000_10ff9c34a357cc30

If you’ve ever looked at the ClickOnce cache, the ClickOnceLocation will be familiar to you, being in the same format as and location as other types of ClickOnce applications.

So the assemblyLocation is where the dll is actually being run from by the hosting application (Outlook in this case), and the ClickOnceLocation is the location of the other deployment files. Any files that you deploy with your VSTO add-in and want to access programmatically can be found there.

MIME Types for ClickOnce deployment

June 12, 2010

When you are hosting a ClickOnce deployment on a webserver, you need have certain MIME types defined so the server knows how to handle the files. You can host a ClickOnce deployment on any server regardless of the operating system. So you can host a ClickOnce deployment on an Apache server just as easily as a server running Microsoft Windows Server. You just need to set up the right MIME types.

When you install the .NET Framework, it registers the MIME types automatically. This is why you don’t have to set up MIME types if you install IIS on your desktop machine and test your deployments by deploying to localhost. Carrying that forward, if you have the .NET Framework installed on your server, the MIME types should already be registered.

This is generally one of the first things to check when you’re having problems downloading the deployment. A definite sign that your MIME types are not set up correctly is if your customers try to install the application and it shows the XML of the deployment manifest (the .application file) in Internet Explorer rather than installing the application.

Here are the basic MIME types you need for every ClickOnce deployment:

.application –> application/x-ms-application
.manifest –> application/x-ms-manifest
.deploy –> application/octet-stream

If you are targeting .NET 3.5 or .NET 4.0, you need these as well:

.msp –> application/octet-stream
.msu –> application/octet-stream

If you are deploying an Office application (VSTO add-in), you need this one:

.vsto –> application/x-ms-vsto

If you are deploying a WPF application, you need these:

.xaml –> application/xaml+xml
.xbap –> application/x-ms-xbap

Click one of these links to see how to set MIME types in IIS 6 or IIS 7.

If your application is hosted on an Apache webserver, you can set up your own MIME types by putting entries in the .htaccess file in the root folder of your deployment. The syntax for adding the MIME types is this:

AddType Mime-type file-extension

For example, for the first three MIME types above, you would add these lines to your .htaccess file:

AddType application/x-ms-application application
AddType application/x-ms-manifest manifest
AddType application/octet-stream deploy

You can create the .htaccess file simply by opening notepad or some other text editor and adding the lines above to it, and saving it with the file name of .htaccess. Then copy it to the root of your deployment folders, and those MIME types will work for that folder and all of its subfolders.

For more information than you ever wanted to know about .htaccess files, check out this article.

Enhanced Logging in ClickOnce Deployment

May 31, 2010

With the .NET 4.0 Framework, Microsoft has added the ability to turn on verbose logging for the install, update, and uninstall of a ClickOnce application. Of course, it doesn’t do much good to modify the amount of logging without providing access to the log file, so they have also added the ability to let you specify the location and name of the output file for the logging. When you define the log file path, it creates or appends to the log file every time a ClickOnce application is installed, updated, or uninstalled. It even works if you are doing programmatic updates.

What’s particularly keen about these features is that they work for any ClickOnce application as long as .NET 4.0 is installed. In other words, if your ClickOnce application targets the .NET 2.0, 3.0, or 3.5 Framework, you can still install the .NET 4.0 Framework on the computer and take advantage of the new logging features. (Installing .NET 4.0 also installs the updated ClickOnce engine, which is more stable.)

Now what you really want to know is what the log files look like, so you’ll know whether this is worth the trouble. I created a small application that has one Windows Form, and I deployed it with ClickOnce. I installed and uninstalled it with verbose logging turned on and with verbose logging turned off. Then I added programmatic updates and let it update asynchronously and restart the application. The logging for automatic updates is the same as an install, but it’s less detailed if you are using programmatic updates. Here are the log files:

ClickOnceLog_Verbose_Install
ClickOnceLog_Verbose_Uninstall
ClickOnceLog_Verbose_Update_Programmatic
ClickOnceLog_NotVerbose_Install
ClickOnceLog_NotVerbose_Uninstall
Zip file of all five log files

Since you’re still reading this, you probably want to know how to turn on the verbose logging and set up the log file. Or at least, how to set up the log file (you may think the verbose logging is too chatty). It’s very simple. You need to add some entries to the Windows registry.

The log file path goes here:
HKEY_Current_User\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\LogFilePath

The registry key for verbose logging goes here:
HKEY_Current_User\Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\LogVerbosityLevel

To add these, open the registry editor and drill down to the Deployment sub-key. Right-click on Deployment and select “New” and then “String Value” as displayed here:

Type in the key name and press Enter. Then press Enter again or double-click on the entry to set the value.

Type in the value and press Enter.

If you want to stop outputting the logging, delete the registry key for the LogFilePath.

If you change or set the LogFilePath, it takes effect immediately. If you change or set the LogVerbosityLevel, it does not take effect until the user logs off and logs back in.

Another fun piece of information: If you set the LogFilePath to an empty string instead of deleting the registry entry, every time you run the ClickOnce application, you will get an error that dfsvc.exe (the ClickOnce service) is no longer working. (I’m thinking that’s not a feature.)

One word of caution: there is no automatic cleanup of the log file, no rollover when it gets to a certain size or anything like that. ClickOnce continues to append to the log file with every install, update, and uninstall. So if you start up the logging on someone’s computer, you must remember to come back around and turn it back off.

If you want to leave the logging turned on, I strongly recommend that you have your application manage the log file. For example, you could rename the log file every time a new one is created, and make sure you only keep the most recent one. You can tell if the application is newly installed or if an update is newly installed by checking the IsNetworkDeployed property of System.Deployment.Application.ApplicationDeployment. If true, the user just installed the application, or installed an update, and you could manage the log file at that time.

Programmatically modifying the registry

Editing the registry is fraught with peril. I remember the old days when you used to be able to call Microsoft for help with Windows (the REALLY old days). One of the first things they would ask you was, “Did you edit the Windows registry?” It only took one phone call to learn that if you said “Yes”, they would not help you. This taught many developers to, well, lie.

So just for grins, I wrote a small .NET application that provides a user interface you can use to change the registry entries for the ClickOnce logging. Basically it displays a screen where you can browse to the folder where you want your log files, and you can turn verbose logging on and off. Here’s what the UI looks like:

You could take this project and set the target Framework to the same as your application. If your user has the .NET Framework installed on their computer, you can just build this application and then copy the executable to the user’s machine and run it to set the values.  Or better yet, put it on a thumb drive and run the executable from there.

Since the registry entries are in HKCU, you could also have your ClickOnce application update these values, but that’s not helpful for the first install!

Let’s take a look at the LogSettings class that does all the work. This is in C#, but the whole solution is available in both C# and VB at the end of this article.

First, here are the namespaces you need to include. Microsoft.Win32 is for the registry changes.

using System.IO;
using Microsoft.Win32;

Next are the constants and properties. This includes the registry sub-key name. When you retrieve the registry sub-key, you specify which hive, such as HKCU, so it is not part of the sub-key name.

//This is where the values go for the ClickOnce logging, in HKEY_Current_User.
private static string ClickOnceLoggingSubKeyName = 
  @"Software\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment";

/// <summary>
///This is the registry value name for the log file path. 
///The value should be the fully-qualified path and file name of the log file.
/// </summary>
private static string rkName_LogFilePath = "LogFilePath";
      
/// <summary>
/// This is the registry value name for the logging level. 
/// The value should be = 1 if you want verbose logging. 
/// To turn off verbose logging, you can delete the entry or set it to 0.
/// </summary>
private static string rkName_LogVerbosityLevel = "LogVerbosityLevel";

/// <summary>
/// Fully-qualified path and name of the log file.
/// </summary>
public string LogFileLocation { get; set; }

/// <summary>
/// Set this to 1 for verbose logging.
/// </summary>
public int LogVerbosityLevel { get; set; }

/// <summary>
/// Set to true if doing verbose logging.
/// </summary>
public bool VerboseLogging { get; set; }

Here is the method called to create an instance of the class; this reads the current entries from the registry and stores them in the class properties. First you open the sub-key, and then you retrieve the two values that you need.

/// <summary>
/// Create a new instance of this class and get the value for the registry entries (if found).
/// </summary>
/// <returns>An instance of this class.</returns>
public static LogSettings Create()
{
  LogSettings ls = new LogSettings();

  //open the Deployment sub-key.
  RegistryKey rk = Registry.CurrentUser.OpenSubKey(ClickOnceLoggingSubKeyName);

  //get the values currently saved (if they exist) and set the fields on the screen accordingly
  string logLevel = rk.GetValue(rkName_LogVerbosityLevel, string.Empty).ToString();
  if (logLevel == "1")
  {
    ls.VerboseLogging = true;
    ls.LogVerbosityLevel = 1;
  }
  else
  {
    ls.VerboseLogging = false;
    ls.LogVerbosityLevel = 0;
  }
  ls.LogFileLocation = rk.GetValue(rkName_LogFilePath, string.Empty).ToString();           
  return ls;
}

And last, but not least, here is the method to save the entries back to the registry. You open the sub-key in write mode, so you can modify the associated values. If the log level is not set to verbose, this deletes the value for the logging level from the registry. If no file name is specified for the log file, this deletes that registry entry. Otherwise, the entries are updated with the values set on the screen.

/// <summary>
/// Save the values to the registry.
/// </summary>
/// <returns></returns>
public bool Save()
{
  bool success = false;
  try
  {
    //Open the Deployment sub-key.
    RegistryKey rk = Registry.CurrentUser.OpenSubKey(ClickOnceLoggingSubKeyName, true);
    //Set the values associated with that sub-key.
    if (this.VerboseLogging)
      rk.SetValue(rkName_LogVerbosityLevel, "1");
    else
    {
      //check to make sure the [value] exists before trying to delete it 
      object chkVal = rk.GetValue(rkName_LogVerbosityLevel);
      if (chkVal != null)
      {
        rk.DeleteValue(rkName_LogVerbosityLevel);
      }
    }

    if (this.LogFileLocation.Length == 0)
    {
      //check to make sure the [value] exists before trying to delete it 
      //Note: If you set the values to string.Empty instead of deleting it,
      //  it will crash the dfsvc.exe service.
      object chkPath = rk.GetValue(rkName_LogFilePath);
      if (chkPath != null)
        rk.DeleteValue(rkName_LogFilePath);
    }
    else
    {
      rk.SetValue(rkName_LogFilePath, this.LogFileLocation);
      string logFolder = Path.GetDirectoryName(this.LogFileLocation);
      if (!Directory.Exists(logFolder))
          Directory.CreateDirectory(logFolder);
    }
    success = true;
  }
  catch
  {
    throw;
  }
  return success;
}

The Visual Studio 2010 solutions can be downloaded in either CSharp or VB.

Download the C# version

Download the VB version

Acknowledgements

Thanks very much to Jason Salameh at Microsoft, who I believe was largely responsible for adding this feature. I believe it will provide some clarity and help with troubleshooting when people have problems installing ClickOnce applications.

[Edit 7/7/2011 Move downloads to Azure blob storage]

[Edit 3/8/2014 Move to different Azure blob storage]

What’s New in ClickOnce Deployment in .NET 4.0

May 23, 2010

There are some cool new features in .NET 4.0 and VS2010 for ClickOnce deployment. I’m going to summarize the new features here and provide some links to the details.

The ClickOnce engine has been updated and strengthened, and the team that supports the runtime says that the errors that seem to have no solution, such as "dfsvc.exe has stopped working", should be fixed. They have also added enhanced logging and the option to set the location (and name) of the ClickOnce log file — no more searching through your Temp folder! What’s cool about these changes is that they apply if you install .NET 4.0 on the computer, regardless of the version your application targets. So if you have a Windows Forms application that targets .NET 2.0 and you are having problems installing it, you can install .NET 4.0 on the computer and turn on the verbose logging. This has already been useful to me, so I will blog about it next.

They have fixed “the certificate problem” in all cases. The problem is discussed in detail here. The basic problem was that when you changed the certificate used to sign the ClickOnce deployment, the customers would have to uninstall and reinstall the application. They fixed this in .NET 3.5 SP-1 if you used automatic updates, but not for programmatic updates, and not for VSTO projects. Now they have fixed it in all cases when targeting the .NET 4.0 Framework.

With VS2010, you can configure your application to target multiple versions of the .NET Framework. Of course, this doesn’t mean it will run on multiple versions – you will have to verify that yourself. To use this feature, you have to manually edit the manifest files and app.config file and re-sign the manifests. For details, click here.

For XBAP applications (browser-hosted WPF applications), these can now elevate to Full Trust just like any other ClickOnce application. For more info, check out this article.

For VSTO projects, you can now deploy multiple Office solutions together with one ClickOnce installation. Like the Three Musketeers, it’s all for one and one for all. All of them are installed together, and if you uninstall through Programs, it uninstalls all of them. 

For VSTO projects, you also now have the ability to do some post-deployment actions. For example, you can create registry keys, modify a config file, or copy a document to the end user computer.

Another interesting change for VSTO projects is if you are targeting the .NET 4.0 Framework, you no longer have to include the Primary Interop Assemblies as a prerequisite. The type information for the PIAs that you use in your add-in is embedded into the solution assembly and used at runtime instead of the PIAs.

And last, but not least — in .NET 3.5 SP-1, they quietly introduced the ability to create your own custom UI for your ClickOnce deployment, while still using the ClickOnce engine to install and update your application. They have improved this feature for .NET 4.0; check out the walkthrough example here.

I think there’s something useful in the new features for everyone. If there are other features you would like to see – other than “install for all users”, which is a complete paradigm shift – please leave a comment. I will summarize the requests and pass them on to the ClickOnce team at Microsoft.

The Future of ClickOnce Deployment

April 18, 2010

People frequently ask about the future of ClickOnce deployment. I hear and read things like “Microsoft hasn’t updated their ClickOnce blog since 2006.” “They never change anything in ClickOnce.” “You never hear anything about ClickOnce deployment updates.” “Are they going to keep supporting it?” “Why doesn’t Microsoft use ClickOnce themselves?”

The answers to those questions are: 1. It’s not sexy so nobody talks about it. 2. Yes they do. .NET 3.5 included ClickOnce deployment for VSTO applications, which is awesome. And SP-1 included optional signing and hashing, file associations, and other fun stuff. 3. You do if you know where to listen. 4. Yes. 5. They use it for many of their apps used internally. I don’t think they can use it for Visual Studio or Office. Can you imagine what the prerequisite list would look like?

Silverlight is sexy. Windows Phone 7 is sexy. WPF is sexy. Deployment? Not so much. The release of Silverlight 4 was even noted on one of the Apple News sites. How does “I created this really cool component that you can embed in a WPF application and it cleans your computer screen and tidies up your desk” compare with “I figured out how to install this really cool component on your computer.” See what I mean?

Deployment is like Amazon.com delivery. You don’t ever think about how your books get from that cool page on the web to your Kindle in two minutes (or, if you’re a traditionalist, to your front porch in two days), but aren’t you excited when they show up?

Even though Microsoft doesn’t go on Oprah to discuss their feelings about ClickOnce deployment, I have discovered over the past few months that they really do care about it.

Saurabh Bhatia, the ClickOnce expert at Microsoft, has been helping me over the past year to respond to some of the more difficult questions in the forums. When I attended the MVP Summit in February, I met with Saurabh and some of the other people who work in and around ClickOnce. The 1-hour meeting stretched into 3-1/2 hours as we discussed feedback and information I had collected from the MSDN Forums, StackOverflow, blog articles, and from individuals who e-mailed me or talked to me after my presentations. I passed on complaints, common problems, and most frequently requested new features. They really wanted to know, and were glad to get the information.

In return, they provided me with a look at what’s coming in .NET 4.0 (that’s the next blog post). Since I’ve returned, they have followed up with answers to my questions. (They were sending me e-mails with answers before I’d even left Washington!) Saurabh and one of his cohorts, Jason Salameh, continue to provide resources to help me support the ClickOnce Deployment community, and Saurabh set up regular meetings just to touch bases and help me with any difficult issues or questions that come up that I can’t answer. I think of it as a “Stump Saurabh!” session, but so far I’ve only managed to stump him once (proxy authentication). I learn something new with every conversation.

Also coming soon is an update of the Patterns and Practices Smart Client Software Factory and the ClickOnce documentation for it. (I know that because they asked me to do the update to the docs. I was so flattered!)

It’s safe to say that Microsoft will continue to support and enhance ClickOnce deployment. My next blog post will be a summary of the new features available in .NET 4.0. If there are features you want, post a comment and I’ll pass it along. If you have questions about problems you’re having with ClickOnce deployment, please post a question in the MSDN ClickOnce and Setup & Deployment Forum. I’ll see you there.

How to pass arguments to an offline ClickOnce application

March 21, 2010

In ClickOnce Deployment, it is a common belief that you can not pass arguments to an application unless:

  1. The application is deployed to a web server, and
  2. The application is online-only.

If you are interested in passing query parameters to an online-only application deployed to a web server, check out the article on MSDN. About offline applications, or applications deployed via a file share, that page says this:

 

However, this is no longer the case. I suspect it was changed when they added the ability to do file associations in .NET 3.5 SP-1. It turns out that you can now pass parameters to:

  • an offline ClickOnce application,
  • a ClickOnce application deployed to a file share, and even to
  • an offline ClickOnce application deployed to a file share.

And of course you can pass parameters to an online-only application using query parameters, but we already knew that (see article referenced above).

Here’s how you call the application and pass the arguments:

System.Diagnostics.Process.Start(shortcutPath, argsToPass);

This is how you read the argument string:

//Get the ActivationArguments from the SetupInformation property of the domain.
string[] activationData = 
  AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;

Here are three different ways to pass and receive arguments:

  1. Pass a file path and name as a URI. This mimics what happens when you set up a file association and double-click on a file with that extension.  The argument will start with “file:”.
  2. Pass a query string with key-value pairs in it. This is the same way you pass and parse query strings for an online-only application. The argument will start with a question mark.
  3. Pass one value or a list of comma-delimited values.

Locate the shortcut for the application.

The first thing you need to do is locate the shortcut in the start menu for the application you want to run — this is made up of the Publisher Name and Product Name. Here is how to find the location of the application’s shortcut:

StringBuilder sb = new StringBuilder();
sb.Append(Environment.GetFolderPath(Environment.SpecialFolder.Programs));
sb.Append("\\");
//publisher name is Nightbird
sb.Append("Nightbird");  
sb.Append("\\");
//product name is TestRunningWithArgs
sb.Append("TestRunningWithArgs.appref-ms ");  
string shortcutPath = sb.ToString();

Call the application with an argument string:

System.Diagnostics.Process.Start(shortcutPath, argsToPass);

The argument string that you pass can not have spaces or double-quotes in it. If you pass [a b c], you will only get [a] on the receiving side. If you pass [“a”,”b”,”c”], you will get [a]. If you need to pass arguments with spaces, pass your arguments as a query string.

How to create the argument string to be passed, and how to parse it on the receiving side.

Case 1: Sending file path and name. This mimics what happens when you use the ClickOnce properties to set up a file association, and the user double-clicks on an associated file.

Build the argument string:

//If you have file associations, 
//and you double-click on an associated file,
//it is passed in to the application like this: 
//file:///c:/temp/my%20test%20doc.blah
//So format it as a URI and send it on its way.
string fileName = @"D:\MyPictures\thePicture.jpg";
Uri uriFile = new Uri(fileName);
string argsToPass = uriFile.ToString();

On the receiving side, retrieve the file path from the URI:

//This is what you get when you set up a file association 
//  and the user double-clicks on an associated file. 
Uri uri = new Uri(activationData[0]);
string fileNamePassedIn = uri.LocalPath.ToString();

Case 2: Sending a querystring

Define the argument string:

//querystring 
string argsToPass = "?state=California&city=San%20Francisco";

Parse the string on the other side:

//NameValueCollection is like a dictionary.
NameValueCollection nvc = 
  System.Web.HttpUtility.ParseQueryString(activationData[0]);
//Get all the keys in the collection, 
//  then pull the values for each of them.
//I know I'm only passing each key once, with one value, 
//  in the querystring.
string[] theKeys = nvc.AllKeys;
foreach (string theKey in theKeys)
{
  string[] theValue = nvc.GetValues(theKey);
  //key is theKey
  //value is theValue[0]
  System.Diagnostics.Debug.Print("Key = {0}, Value = {1}", 
    theKey, theValue[0]);
}

Case 3: Pass a list of comma-delimited values

Define the argument string:

//pass a comma-delimited list of values
//don't use any spaces
string argsToPass = "arg1,arg2,arg3,arg4";

Parse the string on the other side:

//I've only ever seen activationData have one entry,
//  but I'm checking for multiples just in case. 
//This takes each entry and splits it by comma 
//  and separates them into separate entries.            
char[] myComma = { ',' };
foreach (string arg in activationData)
{
  string[] myList = activationData[0].Split(myComma, 
    StringSplitOptions.RemoveEmptyEntries);
  foreach (string oneItem in myList)
    System.Diagnostics.Debug.Print("Item = {0}", oneItem);
}

If you only want to send one argument, just use activationData[0].

Summary and Code Samples

This showed three ways to pass arguments to an offline ClickOnce application, and how to parse them on the receiving side.

Code samples can be found here. They are available in both C# and VB. They were built with VS2008 and they target .NET 3.5 SP-1, which is the minimum version for which this will work.

The first solution (RunningWithArgs) receives the arguments, parses them, and displays them in a listbox. You need to deploy this one to a webserver or a file share and then install it. To determine which kind of argument it is receiving, it checks the first characters of the argument string.

The second solution (CallRunningWithArgs) calls the first one and passes arguments to it. It uses the conventions mentioned previously to determine the kind of arguments being passed. If the first application is installed, you can just run this one out of Visual Studio.

How to deploy the SQLServer Compact Edition software locally

February 28, 2010

If you are using a SQLServer Compact Edition database, you will need to deploy the software for it. You can deploy it as a prerequisite, but this requires the customer to install one more thing without clicking Cancel. He must also have administrative privileges.

Additionally, version control is more difficult with ClickOnce deployment, because prerequisites are not handled by the incremental updates, and you can never get all of your customers to reinstall a prerequisite. At least one of them will be out of town, and when they come back, they’ll have too much e-mail to deal with and won’t take the time to follow your instructions to update their application.

A better idea is to deploy the assemblies required locally. This video explains all.

In the interest of copy and paste and making your life easier, here are the 7 dll’s you need to locate and add to your project. You should be able to find them in C:\Program Files\Microsoft SQL Server Compact Edition\v3.5 (or v3.0). If you are using 3.0, substitute “30” for “35” in the names of these dll’s.

  • sqlceca35.dll
  • sqlcecompact35.dll
  • sqlceer35EN.dl
  • sqlceme35.dll
  • sqlceoledb35.dll
  • sqlceqp35.dll
  • sqlcese35.dll

Set the Build Action to “Content”, and “Copy to Output Directory” to “copy always” on these entries in Solution Explorer. On the reference to the System.Data.SqlServerCe.dll in your project, set “Copy Local” to “true”.

In other news about SQLCE databases (or any database, for that matter), if you are deploying one with ClickOnce deployment as data, did you know if you open the database it changes the timestamp, and deploys a new version next time you deploy an update? On the client side, it then copies the previous version to the ApplicationData\pre folder, and puts the new one to the ApplicationData folder. If you’re not handling this in your code, it looks like the user has lost his data. This kind of thing tends to annoy customers, which is always a bad thing that can lead to a loss of cash flow, if you know what I mean.

For a easier way to handle updates and let your database change only when you intend it to, you might want to check out my blog entry titled Where do I put my data to keep it safe from ClickOnce updates? I think the title is self-explanatory.

How to extend an existing certificate, even if it has expired

January 26, 2010

In many cases, when the certificate you use to sign your ClickOnce deployment expires, your customers have to uninstall and reinstall the application. This is the problem discussed in my MSDN article on Certification Expiration in ClickOnce Deployment.

Part of that article discusses the use of a program called RenewCert to extend your signing certificate, and tells you why you might want to do that. The following video shows you how.

The basic command looks like this:

RenewCert oldpfxfile newpfxfile CN=newName password-to-old-pfx-file

Let’s say I have a certificate called NightbirdPFX.pfx that has expired. If I want to create a new version with the same public/private key pair, I would use this command:

RenewCert NightbirdPFX.pfx NightbirdPFX5Yrs.pfx CN=”Nightbird 5 Yrs” MYpassw0rd

Now I can replace the old certificate with the new and publish an update, and the users will be able to pick up the update without having to uninstall and reinstall the application.

As noted in the GoldMail above, if you have a vendor certificate, it does change it to a test certificate. So if you still want to have a trusted deployment, you have to purchase a new certificate.

The compiled version of RenewCert that I used to extend my vendor certificate can be downloaded here. This requires the C runtime libraries from Visual Studio 2005 in order to run, so I have included those in the zip file as well.

I’d like to thank Cliff Stanford for taking the C++ code from MSDN and enhancing it so it works for both test certificates and vendor certificates. If you’re interested, the code and compiled binary can be found here.

If you want to read the original article in MSDN and/or see Microsoft’s code, you can find it here.

[Edit 7/7/2011 Move zip file to Azure blob storage]

How to move a ClickOnce deployment

January 17, 2010

One of the questions I see in the MSDN ClickOnce Forum is how to move a ClickOnce deployment to a different location. You might want to move your deployment because your company is setting up a new webserver, or because you change hosting companies for your deployment.

You can’t just change the URL because it’s part of the security built in to ClickOnce. It checks the update location for the installed application against the URL for the update, and if they don’t match, it won’t install the new version. This keeps someone from hijacking your deployment and substituting their own files.

This video will show you how to move your deployment to a different URL.

This download contains the source code (VS2008, C#) for the three versions of the application used in the video. If you are a VB developer and can’t figure out how to translate the code to VB, please post a comment and I’ll post a VB version of the code.

 Click here to get the source code.

(edit) I couldn’t see how this could work with an Office Solution (VSTO), so I did some looking around. VSTO doesn’t use the update URL at all, so it looks like your customers have to uninstall and reinstall if you have to move a VSTO deployment. Dang.

Installing a ClickOnce Application for all users

September 7, 2009

I gave a talk in Mountain View at the South Bay .Net User Group meeting on August 5th. This was a general talk about ClickOnce Deployment and how to use it. Almost everyone in the room was a Windows Forms or WPF developer, which seems rarer and rarer these days as people migrate to web applications. There were a lot of questions, and a lot of good discussion about things that people would like changed in ClickOnce deployment.

The most frequently requested feature in that meeting and in the MSDN ClickOnce Forum is to install a ClickOnce application for all users rather than a specific user.

This is difficult because the files are stored in the user’s profile, where the user has read/write privileges. One of the design goals of ClickOnce is to provide a deployment technique that allows customers to install applications without elevated privileges. Installing an application for all users requires privileges.

Another design goal was to protect the client machine from problems caused by software installations. Do you remember “dll hell”? This was the problem that happened when another application came along and replaced a dll that you were dependent on, and caused problems for your application, or vice versa.

In a ClickOnce deployment, it is possible to include most of the dll’s locally with the deployment, rather than installing them in the GAC or the windows system directory. (This is excluding, of course, the .NET Framework and other prerequisite applications such as SQLServer Express.) You can even deploy the SQL Compact Edition dll’s or DirectX dll’s. This allows you to maintain strict version control on the dll’s that you include in your deployment. Microsoft would have to figure out a way to handle this if the application were deployed for all users; if they weren’t careful, you could easily end up in dll hell again.

On the other hand, if they just chose to put the files under the All Users profile, once again you have the permissions problem that Microsoft sought to handle with ClickOnce deployment, because the user can’t write to those files without elevated privileges. Also, If Microsoft stored the deployed files in the “All Users” folder, they would have to figure out what to do if UserA was logged on using the application, and then UserB logged on and there was an update available. Do you kick off UserA? Do you forego the update because UserA is already running the application? What is UserA always leaves his account logged on? How will you ever do an update?

This request has been passed on to Microsoft, but I haven’t heard of any plans to include a change to this in .NET 4.0, so I wouldn’t expect any quick results. Doing an all-users install is counter to the design goals of ClickOnce deployment, so I think it’s going to be something everyone has to live with, at least for now. Using XCopy or a setup & deployment package and rolling your own incremental update methodology would be the way to go if you absolutely have to have an All Users installation.