Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stub Executables #868

Merged
merged 14 commits into from
Nov 30, 2016
Merged

Stub Executables #868

merged 14 commits into from
Nov 30, 2016

Conversation

anaisbetts
Copy link
Contributor

@anaisbetts anaisbetts commented Nov 2, 2016

This PR changes the strategy we use for creating shortcuts. To work around the fact that our executable moves around, we've made shortcuts point to Update.exe with special parameters. Unfortunately, Windows just does not like this:

  • File associations get busted on upgrade
  • Pinning gets fucked
  • Windows gets hella confused about the icon
  • Windows Firewall stuffs
  • Users are confused that they can't edit shortcuts the way they expect

Instead, during the build process, we're going to create a "stub" executable for every app in your package, and put it in the directory above it. All the shortcuts / file associations / w/e get pointed to that instead of the real EXE, and Windows stops losing its mind about apps moving around.

Stub executables are a copy of all of the executables in the folder, and simply call the latest version of the executable with the same name.

@anaisbetts
Copy link
Contributor Author

I forgot what else I wanted to do with this

@ackvf
Copy link

ackvf commented Jan 17, 2017

One other problem that may be affected (solved) by this PR is custom url protocol mappings (like callto: etc.)

@simquad
Copy link

simquad commented Jan 25, 2017

Is the squirrel process supposed to be generating this stub? I'm having problems with shortcuts and believe this may be the issue.

My install works first time, but after updating, it does not.
[Caused by incorrect version of Squirrel.exe on build server]

@afreeland
Copy link

afreeland commented Jan 26, 2017

I appear to be having issues with the stub executable as well, it doesn't want to actually launch the application. Which also means that the shortcut doesn't work as desired. I think this stub executable is the last piece I need to get this thing running =)

I will say there is some unadulterated joy seeing my app update from a delta :shipit:

@simquad
Copy link

simquad commented Jan 26, 2017

@afreeland - I'm having an issue with the stub executable at the minute too (:squirrel: 1.5.2)

It seems to have replaced my application exe in the app-1.0.0 directory with the stub executable itself; which seems weird.

Copying the correct exe (from a similar Visual Studio build), into the folder, then launching the stub, makes it work.

Is this an issue known in the stub step?

@afreeland
Copy link

@simquad My .exe works in the app-x.x.x directory but the generated .exe, up a level, doesnt work for me...which is where the shortcut targets.

@simquad
Copy link

simquad commented Jan 26, 2017

@afreeland - Neither works for me sadly. Both of the executables are the same (stub and the "proper" one in the app-x.x.x directory).

I confirmed this with BeyondCompare.

Checking the server contents of the nupkg, I can see it does have an exe in it of the correct size; this must be happening on install?

@simquad
Copy link

simquad commented Jan 26, 2017

I've resolved my issues, big thanks to comments from @lukeskinner in #918

The issue was I had ClickOnce Security still checked, causing an extra exe to appear in app.publish.

This article on stackoverflow assisted: http://stackoverflow.com/questions/30142229/what-creates-the-directory-app-publish-in-visual-studio-2013

image

Not sure if @paulcbetts would see this as a bug, or whether user error for the app.publish being there in the first place.

Either way, I'm now a happy developer again!

@anaisbetts
Copy link
Contributor Author

Hey @simquad, can you file a separate bug about this? While this is an odd case, Squirrel still shouldn't blindly be paving your actual executables with stubs

@dkonik
Copy link

dkonik commented Aug 9, 2020

@anaisbetts Is this supposed to resolve the firewall exception request every update issue? I'm using v 1.9.1 and still running into this, the firewall creates the rule for the exe in the specific version (e.g. app-0.0.1). I looked around but couldn't find any extra configs I was supposed to set or anything.

@dkonik
Copy link

dkonik commented Aug 10, 2020

Okay, in case anyone else runs into this issue, I came up with a workaround that at least satisfies my needs. I'm not going to call it pretty, but you basically check (on app launch) whether you are the "current" folder, and if not, you copy the folder that the current exe is in, and then start that copied exe and return from the current one. Couple of things to note about this approach:

  • Using this method means you will have to manually create your own shortcuts, but since you are always running the exe from the same location, you only have to do it once and don't have to worry about deleting the old one after an update
  • Unlike I am doing in the below snippet, you will probably want to handle the "uninstall" event so that you can delete the shortcuts you added
  • Don't set DesiredAppFolderName "app-current". Squirrel doesn't like that, and it will fail to uninstall.
public const string DesiredAppFolderName = "current";
private const int AttemptDurationSeconds = 20; // The duration of time before giving up trying to copy the current exe

private string currentExeLocation = null;
private string appLocalRoot = null;
private string fullDesiredCurrentPath = null;

[STAThread]
public static void Main()
{
    try
    {
        // All of these default to null, which is what we want since we don't want to create a shortcut
        // from an app that squirrel launches as it will be in the folder app-[some-version]
        // NOTE: You probably want to handle the uninstall event to remove any shortcuts, etc. that you
        // manually added
        SquirrelAwareApp.HandleEvents();

        currentExeLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        appLocalRoot = Directory.GetParent(CurrentExeDirectory).FullName;
        fullDesiredCurrentPath = Path.Combine(appLocalRoot, DesiredAppFolderName);

        if (!currentExeLocation.ToLower().Contains(DesiredAppFolderName))
        {
            // We're not in the desired "current" folder, so copy to the current folder
            if (MoveToDesiredCurrentFolder()) 
            {
                // We successfully copied the new version over to the desired current folder,
                // so kick that process off and exit this one
                ProcessStartInfo psi = new ProcessStartInfo();
                psi.FileName = System.AppDomain.CurrentDomain.FriendlyName;
                psi.WorkingDirectory = fullDesiredCurrentPath;
                Process.Start(psi);
                return;
            }
            else 
            {
                // The copying failed, handle this how it makes sense for your app,
                // maybe just let this current exe run so that the user at least can
                // have *something*
            }
        }

        // We are in the "current" folder, so business as usual
        var application = new App();
        application.InitializeComponent();
        application.Run();
    }
    catch (Exception e)
    {
        TelemetryManager.TrackException("Main failed", e);
    }
}

And then the helper functions just for code completeness

public static bool MoveToDesiredCurrentFolder()
{
    try
    {
        DateTime giveUpTime = DateTime.Now + TimeSpan.FromSeconds(AttemptDurationSeconds);

        // Hot looping here without a sleep isn't the best way of handling this,
        // so you probably want to do something better
        while (DateTime.Now < giveUpTime)
        {
            try
            {
                DirectoryCopy(currentExeLocation, fullDesiredCurrentPath, true);
                return true;
            }
            catch
            {
                // If the system is bogged down, it might take a bit to release all resources, so
                // try again
            }
        }

        // Copying attempts timed out
        return false;
    }
    catch (Exception e)
    {
        TelemetryManager.TrackException("Failed to move app to current", e);
        return false;
    }
}

// This is just a slightly modified version of https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories
// which deletes the destination directory before copying over, which is what we want in this case
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
{
    // Get the subdirectories for the specified directory.
    DirectoryInfo dir = new DirectoryInfo(sourceDirName);

    if (!dir.Exists)
    {
        throw new DirectoryNotFoundException(
            "Source directory does not exist or could not be found: "
            + sourceDirName);
    }

    DirectoryInfo[] dirs = dir.GetDirectories();

    // If folder already exists, delete it
    if (Directory.Exists(destDirName))
    {
        Directory.Delete(destDirName, true);
    }

    Directory.CreateDirectory(destDirName);

    // Get the files in the directory and copy them to the new location.
    FileInfo[] files = dir.GetFiles();
    foreach (FileInfo file in files)
    {
        string temppath = Path.Combine(destDirName, file.Name);
        file.CopyTo(temppath, false);
    }

    // If copying subdirectories, copy them and their contents to new location.
    if (copySubDirs)
    {
        foreach (DirectoryInfo subdir in dirs)
        {
            string temppath = Path.Combine(destDirName, subdir.Name);
            DirectoryCopy(subdir.FullName, temppath, copySubDirs);
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants