As you may have gathered from my last post, I’ve been playing around with ClickOnce a bit lately.  The latest head-scratcher is how to get a ClickOnce deployed application to run at startup.  It should be a no-brainer (just copy a shortcut in the startup folder, right?) but when you add Vista to the mix all hell breaks loose (and when I say “all hell breaks loose” I mean nothing happens at startup.)  I finally sat down this weekend and put together a sample application and some documentation to support my findings.  Any help or feedback is greatly appreciated.  The main goal is to determine a reliable, reusable way to add AutoStart functionality to any ClickOnce deployed application for any Windows operating system.  Again, piece of cake, right?

Quick Overview

An application shortcut is copied to the Start Menu Programs directory as part of the installation process for a ClickOnce application which is configured to be available offline1. For example, for me on Vista, the attached sample application’s shortcut is placed in the following location: C:\Users\Ben Griswold\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Ben Griswold\ClickOnceAutoStart.appref-ms. 

The shortcut location and file name are based on the Publisher name (Ben Griswold) and the Product name (ClickOnceAutoStart) as defined in the project’s Publishing Options2.

clip_image002

It is important to note that the shortcut’s location and file name are NOT based on the executing assembly’s Company and Product values3, however, the Publishing Options and Assembly Information values are often identical (as is the case in the attached sample application.)

clip_image004

Though Visual Studio provides a means to add an application shortcut to the products folder in the Start Menu Programs directory as well as the Desktop4, there does not appear to be an out-of-the-box means to add a shortcut to Startup Folder. In other words, there is no configuration setting to enable the ClickOnce application to run at startup.

ClickOnce AutoStart Solutions

There are two sited ways to implement run at startup functionality. Both options, however, come with drawbacks and issues.

Solution 1

Programmatically copy the application’s Start Menu Program shortcut (the Application Reference file) into the Startup folder.

clip_image006

Start Menu\Programs\Ben Griswold

clip_image008

Start Menu\Programs\Startup

Issue 1 – There doesn’t appear to be a reliable way to determine where the ClickOnce installer placed the Start Menu Program .appref-ms shortcut. As stated above, the shortcut location is based on the Publishing Options. Can one programmatically read the Publisher and Product name? Is this information is available via the AppDomain.ActivationContext Property? The bottom-line is, if the shortcut location can’t be determined then the file can’t be copied.

Arguably, this isn’t a serious issue since the shortcut location could be “pre-calculated” and stored elsewhere in the application. For example, in Application Settings or within AssemblyInfo which seems to be the de facto standard. All the same, since this Publisher and Product are displayed on the Deployment Web Page5, it is reasonable to assume the values must be exposed.

clip_image010

Issue 2 – Copying the Application Reference shortcut into the Startup folder doesn’t work reliably in Windows Vista. This is the case whether UAC is enabled or not. Interestingly, the same shortcut can successfully launch the application if it is manually executed by the user, but under standard OS startup the shortcut is never triggered.

Issue 3 – Another issue is tied to the uninstall. Is there a way to hook into the uninstall of a ClickOnce application? If not, there doesn’t appear to be a way to remove any/all artifacts which may have been programmatically added to the end users system. In other words, after the product uninstall, it would become the end user’s responsibility to remove the shortcut file which was programmatically copied into the Startup folder. Coincidently, the Start Menu Programs shortcut and desktop shortcut are all removed when the ClickOnce application is uninstalled.

Solution 2

Programmatically create a URL shortcut file which maps to the ClickOnce application’s update location and place it in the Startup folder. I am happy to say this method does work reliably in Windows Vista.

Issue 1 – Though creating an URL shortcut file which maps to the ApplicationDeployment.CurrentDeployment.UpdateLocation.AbsoluteUri works consistently, an empty browser window is launched when the Uri is being queried. Again, this solution works but it isn’t particularly clean.

Issue 2 – Same uninstall issue as outline above.

Sample Application

The accompanying sample application illustrates how to enable run at startup using either of the two techniques stated above. One may validated the functionality and issues by launching the application (after installing via ClickOnce) and then enabling the AutoStart options, verifying shortcut are valid and testing the AutoStart functionality per a system restart.

clip_image012

Again, any help or feedback is greatly appreciated. The main goal is to determine a reliable, reusable way to add AutoStart functionality to any ClickOnce deployed application for any Windows operating system.

Download ClickOnce AutoStart Sample:ClickOnceAutoStart.zip

References:

ClickOnce Startup Folder

Tip: ClickOnce Deployment

1 Project Properties > Publish > Install Model and Settings

2 Project Properties > Publish > Install Model and Settings > Options… > Description

3 AssemblyInfo.cs or Properties > Application > Assembly Information…

4 Project Properties > Publish > Install Model and Settings > Options… > Manifest > Create desktop shortcut

5 Project Properties > Publish > Install Model and Settings > Options… > Deployment.

 

30 Comments to “ClickOnce Run at Startup”

  1. tim says:

    The uninstall problem is a real annoyance on this one.

  2. Ben Griswold says:

    Keep checking back, Tim. I’m hopeful I’ll be posting a resolution in the next couple of weeks — fingers crossed.

  3. Brian Jones says:

    Here is how I create short cuts for a ClickOnce app. It uses System.Reflection and the Environment.GetFolderPath. Uninstall still leaves the shortcuts, however.

    Assembly assembly = Assembly.GetEntryAssembly();
    string company = string.Empty;
    string description = string.Empty;

    if (Attribute.IsDefined(assembly, typeof(AssemblyCompanyAttribute)))
    {
    AssemblyCompanyAttribute ascompany = (AssemblyCompanyAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyCompanyAttribute));
    company = ascompany.Company;
    }
    if (Attribute.IsDefined(assembly, typeof(AssemblyDescriptionAttribute)))
    {
    AssemblyDescriptionAttribute asdescription = (AssemblyDescriptionAttribute)Attribute.GetCustomAttribute(assembly, typeof(AssemblyDescriptionAttribute));
    if (description == “”)
    description = appName.Replace(“.exe”, “”);
    }
    if (!string.IsNullOrEmpty(company))
    {
    string desktopPath = string.Empty;
    string startUp = string.Empty;
    string startMenu = string.Empty;

    desktopPath = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), “\\”, description, “.appref-ms”);

    startUp = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Startup), “\\”, description, “.appref-ms”);
    startMenu = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu), “\\”, description, “.appref-ms”);

    shortcutName = string.Empty;
    shortcutName = string.Concat(Environment.GetFolderPath(Environment.SpecialFolder.Programs), “\\”, company, “\\”, description, “.appref-ms”);

    try
    {
    System.IO.File.Copy(shortcutName, desktopPath, true);
    }
    catch
    {

    }
    try
    {
    System.IO.File.Copy(shortcutName, startUp, true);
    }
    catch
    {

    }

    try
    {
    System.IO.File.Copy(shortcutName, startMenu, true);
    }
    catch
    {

    }
    }

  4. Ben Griswold says:

    @Brian Thanks for providing your sample. You will find similiar logic inside of the downloadable application included with this post. I’ve fallen back to using the executing assembly’s Company and Product values as well, but these values aren’t necessarily correct as ClickOnce uses the Publishing Options (not Assembly Information) to define file names and paths. In my opinion, that’s the big gotcha. Let me know if I’m missing anything. Thanks again.

  5. Andy Copson says:

    Hi, Just thought I’d leave a quick post in case youve not already resolved the issue yourself.

    I have a similar issue trying to resolve the ‘publisher/suiteName/product’ information – these values are required if you need to launch a click once application programmatically, the information in the assembly does not reliably describe the ‘start menu’ path to the click once app.

    The information you need is in the .application deployment manifest which is located at the URI where the application is installed from.

    I have written some c# code to extract this information into a ‘DeploymentDescription’ class which has ‘Publisher’,'SuiteName’ & ‘Product’ properties. The code employs the services of an InPlaceHostingManager to asynchronously aquire the manifest from the application host URI. It is then possible to use an XmlReader to extract the required info.

    Post back if you would like the source code.

    Cheers,

    Andy

  6. Andy Copson says:

    Hi Ben,

    Here’s the code. You’ll notice the GetDeploymentDescription() method is asynchronous so you may want to wire up an event to fire when the information is loaded. In my case the code is contained within a class that has a string field ‘startMenuCommand’ which is set when the deployment information has been loaded.

    Hope this works ok for you.

    Cheers,

    Andy

    // utility class to extract publisher/suiteName/Product

    public class DeploymentDescription
    {
    private const string descriptionElement = “description”;
    private const string publisherAttribute = “publisher”;
    private const string suiteNameAttribute = “suiteName”;
    private const string productAttribute = “product”;
    private string publisher;
    private string suiteName;
    private string product;

    public DeploymentDescription(XmlReader xmlDeploymentManifest)
    {
    ExtractDescriptions(xmlDeploymentManifest);
    }

    private void ExtractDescriptions(XmlReader appManifest)
    {
    while (appManifest.Read())
    {
    if (appManifest.NodeType == XmlNodeType.Element)
    {
    if (appManifest.Name == descriptionElement)
    {
    appManifest.MoveToFirstAttribute();
    do
    {
    if (appManifest.Name.Contains(publisherAttribute))
    publisher = appManifest.Value;
    else if (appManifest.Name.Contains(suiteNameAttribute))
    suiteName = appManifest.Value;
    else if (appManifest.Name.Contains(productAttribute))
    product = appManifest.Value;
    } while (appManifest.MoveToNextAttribute());
    return;
    }
    }
    }
    }

    public string Publisher
    {
    get { return publisher; }
    }

    public string SuiteName
    {
    get { return suiteName; }
    }

    public string Product
    {
    get { return product; }
    }
    }

    // utility class to build executable command

    public class StartMenuCommandBuilder
    {
    private readonly string command;

    public StartMenuCommandBuilder(DeploymentDescription deploymentDescription)
    {
    if (deploymentDescription == null) throw new ArgumentNullException(“deploymentDescription”);
    command = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
    if (!string.IsNullOrEmpty(deploymentDescription.Publisher))
    command = Path.Combine(command, deploymentDescription.Publisher);
    if (!string.IsNullOrEmpty(deploymentDescription.SuiteName))
    command = Path.Combine(command, deploymentDescription.SuiteName);
    command = Path.Combine(command, deploymentDescription.Product);
    command = Path.ChangeExtension(command, “.appref-ms”);
    }

    public string Command
    {
    get{return command;}
    }
    }

    // your code should do something like ……

    if (ApplicationDeployment.IsNetworkDeployed)
    {
    GetDeploymentDescription(ApplicationDeployment.CurrentDeployment);
    }

    // method to asynchronously retrieve ClickOnce ‘Descriptions’ data from host URI

    private void GetDeploymentDescription(ApplicationDeployment currentDeployment)
    {
    var inPlaceHostingManager = new InPlaceHostingManager(currentDeployment.UpdateLocation, false);
    inPlaceHostingManager.GetManifestCompleted += ((sender, e) =>
    {
    var deploymentDescription = new DeploymentDescription(e.DeploymentManifest);
    var commandBuilder = new StartMenuCommandBuilder(deploymentDescription);
    startMenuCommand = commandBuilder.Command;
    });
    inPlaceHostingManager.GetManifestAsync();
    }

  7. Ben Griswold says:

    @Andy – Thanks for your comments and thanks for sharing your code. This issue (particularly the common misunderstanding of where the start menu information is derived) has been driving me nuts for weeks. I can’t tell you how excited I am that you know how to get at the “real” ClickOnce publisher and product values and I can hardly wait to play around with your code tonight. Thanks for the contribution!

  8. Dipesh A. says:

    Solution is may not be as reliable in Vista if you look closer..

    If the user is offline when logging on to windows, the blank IE windows opens (is it actually trying to connect to the server specified in the short cut) and “Page cannot be displayed” pops up. Everything stops here.

    This is not reliable because majority of the users wont be connected to the internet when they are logging on to windows unless they are on a network.

  9. Ben Griswold says:

    @Dipesh A. – That’s a valid point regarding URL Shortcut solution for Vista. Assuming the user has an Internet connection, it will work just fine, but, you’re right, the solution doesn’t gracefully handle itself in the case of the user who is working offline. Thanks for the comment.

  10. Mark Fulton says:

    Why don’t you use a batch file? For example, a file named “AutoStartClickOnceApp.bat” would contain the following single line of text: “C:\Documents and Settings\[User Name]\Start Menu\Programs\[ClickOnce Application Name].appref-ms”

    It should be that simple! Sure, you’ll get a Command Prompt window that will appear for a brief moment but that is typical of MS Windows (i.e. logging into some domain).

    I hope this helps. Good luck!

  11. Er.. what about simply updating the registry to put the path of the executing assembly in HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run?

    This will work on Vista and XP. When the app updates you can simply update the registry with the new exe path (since it changed on every deployment).

  12. Ben Griswold says:

    @Mark Fulton – That’s an interesting idea. So, I would have my ClickOnce application create a bat file with the appropriate launch information (appref-ms reference) rather than including the shortcut to the appref-ms file in the Startup folder itself. I’ll give it a shot.

    @Dirtel Minder – Unfortunately that approach doesn’t work on Vista for ClickOnce hosted applications. It doesn’t work, however, if the application were running as a non-ClickOnce deployed application.

  13. thijs says:

    @Ben Did you try out the batch file solution? Did it work?

  14. thijs says:

    I used the executable of my application to launch itself via the appref-ms shortcut. This doesn’t require a browser and works fine on vista. I’ll post a piece on this one on my blog. I didn’t solve the uninstall issue yet.

  15. Logan Waggoner says:

    Here is what I’m using, I’m pretty sure it is going to work but I haven’t fully tested it. I’ve got a lot more hard coding in here than you would want but that can be gathered easily if necessary. (Publisher name, resultant executable, name you can choose for the key can also be derived from the assembly) etc…

    You need to import Microsoft.Win32

    Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(“Software”)
    key = key.OpenSubKey(“Microsoft”)
    key = key.OpenSubKey(“Windows”)
    key = key.OpenSubKey(“CurrentVersion”)
    key = key.OpenSubKey(“Run”, True)
    If key.GetValue(“YOUR_KEY_NAME”) Is Nothing Then

    If System.IO.Directory.Exists(“C:\Users”) Then
    key.SetValue(“YOUR_KEY_NAME”, “C:\Users\” + Environment.UserName + “\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\”+”PUBLISHER_NAME_HERE” + “\” + “YOUR_EXECUTABLE”)
    Else
    key.SetValue(“YOUR_KEY_NAME”, “C:\Documents and Settings\” + Environment.UserName + “\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\”+”PUBLISHER_NAME_HERE” + “\” + “YOUR_EXECUTABLE”)

  16. Logan Waggoner says:

    Here is a simplified version with a few bug fixes from above, only think that would make this a little better would be pulling the app info and publisher info, this just uses the Assembly info.

    Dim key As RegistryKey = Registry.CurrentUser.OpenSubKey(“Software”)
    key = key.OpenSubKey(“Microsoft”)
    key = key.OpenSubKey(“Windows”)
    key = key.OpenSubKey(“CurrentVersion”)
    key = key.OpenSubKey(“Run”, True)
    Dim rootdir = “”
    If System.IO.Directory.Exists(“C:\Users”) Then rootdir = “C:\Users\” Else rootdir = “C:\Documents and Settings\”

    key.SetValue(“QBCommenter”, rootdir + Environment.UserName + “\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\” + My.Application.Info.CompanyName + “\” + My.Application.Info.ProductName + “.appref-ms”)

  17. Ernest Cook says:

    I have the uninstall issue but for a different reason…

    I am publishing a new version of my app that specifies a specific CPU in order to support 64 bit systems. This “breaks” the clickonce automatic update. My workaround is to provide users the URL to launch the latest version and then, since it will have created a 2nd application, I want to programaitically uninstall the old version.

    Any update on solving the uninstall issue?

  18. mike says:

    suggested workaround
    click once can create desktop shortcut on update

    when onload copy the new desktop shortcut to the applications executing assembly folder then delete desktop one if necessary

    create registry run key with value set to the shortcut path now residing in the executing assembly
    ie my_new_key c:\users\what-en-what\appref-ms

    on restart isnetworkdeployed = true

    hope this helps

  19. Deo says:

    I used the batch file and the application start up fine. I am using it an terminal server. the problem is if there is an update on the application, it log off and it does not update the application.

  20. Great blog post, I have been looking for something like that..

    Rebekah

    yutube

  21. Andy-HR says:

    In Publish Options of project (your first picture)
    you can write :
    Publisher name : Startup
    Suite name : –leave blank–

    Product name : Your product name.

    This will put ClickOnce program link directly to startup :)

  22. Roy says:

    Just thought I’d throw this out there to the interwebs incase someone else googles this. Copies the appref-ms to all users startup. MUST BE ADMIN after that gravy for other users. It works on XP and I’ll probly test something similar out on Win7 a bit later as for Vista, L to the O to the L, why bother.

    Dim userName As String
    userName = Environment.UserName

    If My.Computer.FileSystem.FileExists(“C:\Documents and Settings\All Users\Start Menu\Programs\Startup\YOURAPP.appref-ms”) Then

    Else

    My.Computer.FileSystem.CopyFile(“C:\Documents and Settings\” & userName & “\Start Menu\Programs\YOURAPP\YOURAPP.appref-ms”, “C:\Documents and Settings\All Users\Start Menu\Programs\Startup\YOURAPP.appref-ms”, overwrite:=True)

    End If

  23. tim says:

    It’s great to come back to this issue after 2 years and see so much good stuff. Thanks everyone for contributing! (This time I was trying to look for how to get the published name – which in my case is different from the assembly name.)

  24. Rolf says:

    Hi Ben,

    I’m researching an idea that also requires clickonce. It should run on startup and be able to uninstall itself cleanly. I was thinking if it would be possible to install another executable (possibly deployed as a prerequisite) that runs at startup and would be responsible for locating and starting the clickonce app, but wouldn’t be a clickonce app itself.

    It could uninstall itself when the clickonce app is no longer installed or, in my case, the clickonce app could uninstall it before it uninstalls itself.

    I read that with script installers like NSIS it’s possible to do installs without user interaction, so that might still make it a clickonce experience, even though 2 apps are installed. It sounds a bit like a security issue to me so it might not be possible to install such an executable at all with clickonce, but perhaps it isn’t..

    What’s your opinion, would it work in one way or another?

  25. Ranjan says:

    I want to launch an application from start Menu. Pls tell me what is the syntax for this.

    Next thing is, after the application(small window) is launched, it has some checkbox, drop down list, textbox.I want to set some value on it, How to do.

    Thanks a Lot in advance.

  26. Jordan H. says:

    Just wrote a batch script that works by means of solution 2 but is cleaner (minimizes internet explorer so its never seen) and also handles the possibility of clearing the batch file from the startup menu in the first reboot after the click once app is uninstalled.

    The .bat code:

    IF EXIST “%userprofile%\desktop\Application Name.appref-ms” (
    start /min iexplore “URL shortcut file to update location
    ) ELSE (del %0)

    This code checks first on the desktop for application being present but it can be pointed to the AppData directory if desktop icon is not needed.

    It then improves upon the internet explorer cleanliness by loading the application through IE in minimized mode.

    It loads up and closes IE out before the user has a chance to even open IE. Not perfect but the best I’ve found so far.

    Finally, I figured out the removal of the batch script from startup after a uninstalling the ClickOnce App.

    With an Else statement if file does not exist(which it won’t upon the uninstall), the function

    del %0

    deletes the batch file itself and so clears the startup menu of this batch file.

    All that is required is a reboot following the uninstall and the batch file will delete itself.

    All I have to do to make this work is add this batch file to resources in my application(startup.bat or some such file name) and use File.WriteAllText to write it to the start up menu upon loading the app. Every time the application loads, I can check that this startup batch file is still present and rewrite it there if needed.

    Hope this helps and let me know your feedback!

  27. Is there a website that makes it easy to follow blogs and podcasts? I don’t have an iPod, does that matter?.

  28. VILIC says:

    I am stick with this problem these days, but I think a associated file may help.
    I was thinking about create a file association after I had noticed that VS 2010 provide this option. Maybe, maybe.

  29. Joseph Obi says:

    Today, I went to the beach with my kids. I found a sea shell and gave it to my 4 year old daughter and said “You can hear the ocean if you put this to your ear.” She placed the shell to her ear and screamed. There was a hermit crab inside and it pinched her ear. She never wants to go back! LoL I know this is entirely off topic but I had to tell someone!

  30. Jochen Wezel says:

    As we all know now, it’s not a good idea to use the URL link in the start up. But for completeness of this article, here one more reason:

    If a user switches his default browser from IE to Firefox, Firefox is NOT able to correctly launch the URL (it opens a dialog wether to download or to cancel the file)

Leave a Reply

You can wrap your code with [ruby][/ruby] or [python][/python] blocks for syntax highlighting and you can use these traditional tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>