예제 #1
0
        public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, CancellationToken cancellationToken = default)
        {
            if (shortcutDescription == null)
            {
                throw new ArgumentNullException(nameof(shortcutDescription));
            }

            var baseDirectory      = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath);
            var exeName            = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath);
            var packageTitle       = shortcutDescription.NuspecReader.GetTitle();
            var authors            = shortcutDescription.NuspecReader.GetAuthors();
            var packageId          = shortcutDescription.NuspecReader.GetIdentity().Id;
            var packageVersion     = shortcutDescription.NuspecReader.GetIdentity().Version;
            var packageDescription = shortcutDescription.NuspecReader.GetDescription();

            string LinkTargetForVersionInfo(SnapShortcutLocation location, FileVersionInfo versionInfo)
            {
                var possibleProductNames = new[] {
                    versionInfo.ProductName,
                    packageTitle,
                    versionInfo.FileDescription,
                    Filesystem.PathGetFileNameWithoutExtension(versionInfo.FileName)
                };

                var possibleCompanyNames = new[] {
                    versionInfo.CompanyName,
                    authors ?? packageId
                };

                var productName = possibleCompanyNames.First(x => !string.IsNullOrWhiteSpace(x));
                var packageName = possibleProductNames.First(x => !string.IsNullOrWhiteSpace(x));

                return(GetLinkTarget(location, packageName, productName));
            }

            string GetLinkTarget(SnapShortcutLocation location, string title, string applicationName, bool createDirectoryIfNecessary = true)
            {
                var targetDirectory = location switch
                {
                    SnapShortcutLocation.Desktop => SpecialFolders.DesktopDirectory,
                    SnapShortcutLocation.StartMenu => Filesystem.PathCombine(SpecialFolders.StartMenu, "Programs",
                                                                             applicationName),
                    SnapShortcutLocation.Startup => SpecialFolders.StartupDirectory,
                    _ => throw new ArgumentOutOfRangeException(nameof(location), location, null)
                };

                if (createDirectoryIfNecessary)
                {
                    Filesystem.DirectoryCreateIfNotExists(targetDirectory);
                }

                return(Filesystem.PathCombine(targetDirectory, title + ".lnk"));
            }

            logger?.Info($"About to create shortcuts for {exeName}, base directory {baseDirectory}");

            var fileVerInfo = FileVersionInfo.GetVersionInfo(shortcutDescription.ExeAbsolutePath);

            foreach (var flag in (SnapShortcutLocation[])Enum.GetValues(typeof(SnapShortcutLocation)))
            {
                if (!shortcutDescription.ShortcutLocations.HasFlag(flag))
                {
                    continue;
                }

                var file       = LinkTargetForVersionInfo(flag, fileVerInfo);
                var fileExists = Filesystem.FileExists(file);

                // NB: If we've already installed the app, but the shortcut
                // is no longer there, we have to assume that the user didn't
                // want it there and explicitly deleted it, so we shouldn't
                // annoy them by recreating it.
                if (!fileExists && shortcutDescription.UpdateOnly)
                {
                    logger?.Warn($"Wanted to update shortcut {file} but it appears user deleted it");
                    continue;
                }

                logger?.Info($"Creating shortcut for {exeName} => {file}");

                ShellLink shellLink;
                logger?.ErrorIfThrows(() => SnapUtility.Retry(() =>
                {
                    Filesystem.FileDelete(file);

                    shellLink = new ShellLink
                    {
                        Target           = shortcutDescription.ExeAbsolutePath,
                        IconPath         = shortcutDescription.IconAbsolutePath ?? shortcutDescription.ExeAbsolutePath,
                        IconIndex        = 0,
                        WorkingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath),
                        Description      = packageDescription
                    };

                    if (!string.IsNullOrWhiteSpace(shortcutDescription.ExeProgramArguments))
                    {
                        shellLink.Arguments += $" -a \"{shortcutDescription.ExeProgramArguments}\"";
                    }

                    var appUserModelId      = $"com.snap.{packageId.Replace(" ", "")}.{exeName.Replace(".exe", string.Empty).Replace(" ", string.Empty)}";
                    var toastActivatorClsid = SnapUtility.CreateGuidFromHash(appUserModelId).ToString();

                    shellLink.SetAppUserModelId(appUserModelId);
                    shellLink.SetToastActivatorCLSID(toastActivatorClsid);

                    logger.Info($"Saving shortcut: {file}. " +
                                $"Target: {shellLink.Target}. " +
                                $"Working directory: {shellLink.WorkingDirectory}. " +
                                $"Arguments: {shellLink.Arguments}. " +
                                $"ToastActivatorCSLID: {toastActivatorClsid}.");

                    if (_isUnitTest == false)
                    {
                        shellLink.Save(file);
                    }
                }, 4), $"Can't write shortcut: {file}");
            }

            FixPinnedExecutables(baseDirectory, packageVersion, logger: logger);

            return(Task.CompletedTask);
        }

        void FixPinnedExecutables(string baseDirectory, SemanticVersion newCurrentVersion, bool removeAll = false, ILog logger = null)
        {
            if (Environment.OSVersion.Version < new Version(6, 1))
            {
                logger?.Warn($"fixPinnedExecutables: Found OS Version '{Environment.OSVersion.VersionString}', exiting");
                return;
            }

            var newCurrentFolder = "app-" + newCurrentVersion;
            var newAppPath       = Filesystem.PathCombine(baseDirectory, newCurrentFolder);

            var taskbarPath = Filesystem.PathCombine(SpecialFolders.ApplicationData,
                                                     "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar");

            if (!Filesystem.DirectoryExists(taskbarPath))
            {
                logger?.Info("fixPinnedExecutables: PinnedExecutables directory doesn't exist, skipping");
                return;
            }

            var resolveLink = new Func <FileInfo, ShellLink>(file =>
            {
                try
                {
                    logger?.Debug("Examining Pin: " + file);
                    return(new ShellLink(file.FullName));
                }
                catch (Exception ex)
                {
                    var message = $"File '{file.FullName}' could not be converted into a valid ShellLink";
                    logger?.WarnException(message, ex);
                    return(null);
                }
            });

            var shellLinks = new DirectoryInfo(taskbarPath).GetFiles("*.lnk").Select(resolveLink).ToArray();

            foreach (var shortcut in shellLinks)
            {
                try
                {
                    if (shortcut == null)
                    {
                        continue;
                    }
                    if (string.IsNullOrWhiteSpace(shortcut.Target))
                    {
                        continue;
                    }
                    if (!shortcut.Target.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    if (removeAll)
                    {
                        Filesystem.FileDeleteWithRetries(shortcut.ShortCutFile);
                    }
                    else
                    {
                        UpdateShellLink(baseDirectory, shortcut, newAppPath, logger);
                    }
                }
                catch (Exception ex)
                {
                    var message = $"fixPinnedExecutables: shortcut failed: {shortcut?.Target}";
                    logger?.ErrorException(message, ex);
                }
            }
        }

        void UpdateShellLink([JetBrains.Annotations.NotNull] string baseDirectory, [JetBrains.Annotations.NotNull] ShellLink shortcut, [JetBrains.Annotations.NotNull] string newAppPath, ILog logger = null)
예제 #2
0
        public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, CancellationToken cancellationToken = default)
        {
            if (shortcutDescription == null)
            {
                throw new ArgumentNullException(nameof(shortcutDescription));
            }

            var baseDirectory      = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath);
            var exeName            = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath);
            var packageTitle       = shortcutDescription.NuspecReader.GetTitle();
            var authors            = shortcutDescription.NuspecReader.GetAuthors();
            var packageId          = shortcutDescription.NuspecReader.GetIdentity().Id;
            var packageVersion     = shortcutDescription.NuspecReader.GetIdentity().Version;
            var packageDescription = shortcutDescription.NuspecReader.GetDescription();

            string LinkTargetForVersionInfo(SnapShortcutLocation location, FileVersionInfo versionInfo)
            {
                var possibleProductNames = new[] {
                    versionInfo.ProductName,
                    packageTitle,
                    versionInfo.FileDescription,
                    Filesystem.PathGetFileNameWithoutExtension(versionInfo.FileName)
                };

                var possibleCompanyNames = new[] {
                    versionInfo.CompanyName,
                    authors ?? packageId
                };

                var productName = possibleCompanyNames.First(x => !string.IsNullOrWhiteSpace(x));
                var packageName = possibleProductNames.First(x => !string.IsNullOrWhiteSpace(x));

                return(GetLinkTarget(location, packageName, productName));
            }

            string GetLinkTarget(SnapShortcutLocation location, string title, string applicationName, bool createDirectoryIfNecessary = true)
            {
                string targetDirectory;

                switch (location)
                {
                case SnapShortcutLocation.Desktop:
                    targetDirectory = SpecialFolders.DesktopDirectory;
                    break;

                case SnapShortcutLocation.StartMenu:
                    targetDirectory = Filesystem.PathCombine(SpecialFolders.StartMenu, "Programs", applicationName);
                    break;

                case SnapShortcutLocation.Startup:
                    targetDirectory = SpecialFolders.StartupDirectory;
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(location), location, null);
                }

                if (createDirectoryIfNecessary)
                {
                    Filesystem.DirectoryCreateIfNotExists(targetDirectory);
                }

                return(Filesystem.PathCombine(targetDirectory, title + ".lnk"));
            }

            logger?.Info("About to create shortcuts for {0}, base directory {1}", exeName, baseDirectory);

            var fileVerInfo = FileVersionInfo.GetVersionInfo(shortcutDescription.ExeAbsolutePath);

            foreach (var flag in (SnapShortcutLocation[])Enum.GetValues(typeof(SnapShortcutLocation)))
            {
                if (!shortcutDescription.ShortcutLocations.HasFlag(flag))
                {
                    continue;
                }

                var file       = LinkTargetForVersionInfo(flag, fileVerInfo);
                var fileExists = Filesystem.FileExists(file);

                // NB: If we've already installed the app, but the shortcut
                // is no longer there, we have to assume that the user didn't
                // want it there and explicitly deleted it, so we shouldn't
                // annoy them by recreating it.
                if (!fileExists && shortcutDescription.UpdateOnly)
                {
                    logger?.Warn("Wanted to update shortcut {0} but it appears user deleted it", file);
                    continue;
                }

                logger?.Info("Creating shortcut for {0} => {1}", exeName, file);

                ShellLink shellLink;
                logger?.ErrorIfThrows(() => SnapUtility.Retry(() =>
                {
                    Filesystem.FileDelete(file);

                    shellLink = new ShellLink
                    {
                        Target           = shortcutDescription.ExeAbsolutePath,
                        IconPath         = shortcutDescription.IconAbsolutePath ?? shortcutDescription.ExeAbsolutePath,
                        IconIndex        = 0,
                        WorkingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath),
                        Description      = packageDescription
                    };

                    if (!string.IsNullOrWhiteSpace(shortcutDescription.ExeProgramArguments))
                    {
                        shellLink.Arguments += $" -a \"{shortcutDescription.ExeProgramArguments}\"";
                    }

                    var appUserModelId      = $"com.snap.{packageId.Replace(" ", "")}.{exeName.Replace(".exe", string.Empty).Replace(" ", string.Empty)}";
                    var toastActivatorClsid = SnapUtility.CreateGuidFromHash(appUserModelId).ToString();

                    shellLink.SetAppUserModelId(appUserModelId);
                    shellLink.SetToastActivatorCLSID(toastActivatorClsid);

                    logger.Info($"Saving shortcut: {file}. " +
                                $"Target: {shellLink.Target}. " +
                                $"Working directory: {shellLink.WorkingDirectory}. " +
                                $"Arguments: {shellLink.Arguments}. " +
                                $"ToastActivatorCSLID: {toastActivatorClsid}.");

                    if (_isUnitTest == false)
                    {
                        shellLink.Save(file);
                    }
                }, 4), $"Can't write shortcut: {file}");
            }

            FixPinnedExecutables(baseDirectory, packageVersion, logger: logger);

            return(Task.CompletedTask);
        }