diff --git a/.vscode/launch.json b/.vscode/launch.json index 1878a7c..f258c19 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "cwd": "${fileDirname}" }, { - "name": ".NET Core Launch (console)", + "name": ".NET Core Package Microsoft SQL Server Management Studio", "type": "coreclr", "request": "launch", "preLaunchTask": "dotnet: build-cli", @@ -21,7 +21,26 @@ "args": [ "package", "Microsoft.SQLServerManagementStudio", - "--package-folder", "C:\\tools\\packages" + "--package-folder", "C:\\tools\\packages", + "--package-script", + "--installer-context", "system" + ], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console + "console": "internalConsole", + "stopAtEntry": false + }, + { + "name": ".NET Core Create Index", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "dotnet: build-cli", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/WingetIntune.Cli/bin/Debug/net8.0/WingetIntune.Cli.dll", + "args": [ + "generate-index", + "--output-path", "C:\\tools\\packages\\index.json", + "--update-csv", "C:\\tools\\packages\\updates.csv" ], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/GetWtWin32Apps.cs b/src/Svrooij.WinTuner.CmdLets/Commands/GetWtWin32Apps.cs index 615c34c..c9622df 100644 --- a/src/Svrooij.WinTuner.CmdLets/Commands/GetWtWin32Apps.cs +++ b/src/Svrooij.WinTuner.CmdLets/Commands/GetWtWin32Apps.cs @@ -75,6 +75,8 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke CurrentVersion = app.CurrentVersion, SupersededAppCount = app.SupersededAppCount, SupersedingAppCount = app.SupersedingAppCount, + InstallerContext = app.InstallerContext, + Architecture = app.Architecture, LatestVersion = version, }); } diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/NewWtWingetPackage.cs b/src/Svrooij.WinTuner.CmdLets/Commands/NewWtWingetPackage.cs index fdfc85e..17e0a6d 100644 --- a/src/Svrooij.WinTuner.CmdLets/Commands/NewWtWingetPackage.cs +++ b/src/Svrooij.WinTuner.CmdLets/Commands/NewWtWingetPackage.cs @@ -63,6 +63,39 @@ public class NewWtWingetPackage : DependencyCmdlet HelpMessage = "The folder to store temporary files in")] public string? TempFolder { get; set; } = Path.Combine(Path.GetTempPath(), "wintuner"); + /// + /// Pick this architecture + /// + [Parameter( + Mandatory = false, + Position = 4, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Pick this architecture")] + public WingetIntune.Models.Architecture Architecture { get; set; } = WingetIntune.Models.Architecture.Unknown; + + /// + /// The installer context + /// + [Parameter( + Mandatory = false, + Position = 5, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The installer context")] + public WingetIntune.Models.InstallerContext InstallerContext { get; set; } = WingetIntune.Models.InstallerContext.System; + + /// + /// Package as script + /// + [Parameter( + Mandatory = false, + Position = 6, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Package WinGet script, instead of the actual installer. Helpful for installers that don't really work with WinTuner.")] + public bool? PackageScript { get; set; } + [ServiceDependency] private ILogger logger; @@ -102,6 +135,12 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke TempFolder!, PackageFolder, packageInfo, + new WingetIntune.Models.PackageOptions + { + Architecture = Architecture, + InstallerContext = InstallerContext, + PackageScript = PackageScript ?? false + }, cancellationToken: cancellationToken); WriteObject(package); diff --git a/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml b/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml index 85ec0bd..6a05a2c 100644 --- a/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml +++ b/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml @@ -1277,6 +1277,42 @@ None + + Architecture + + Pick this architecture + + Architecture + + Architecture + + + None + + + InstallerContext + + The installer context + + InstallerContext + + InstallerContext + + + None + + + PackageScript + + Package WinGet script, instead of the actual installer. Helpful for installers that don't really work with WinTuner. + + Boolean + + Boolean + + + None + @@ -1340,6 +1376,42 @@ None + + Architecture + + Pick this architecture + + Architecture + + Architecture + + + None + + + InstallerContext + + The installer context + + InstallerContext + + InstallerContext + + + None + + + PackageScript + + Package WinGet script, instead of the actual installer. Helpful for installers that don't really work with WinTuner. + + Boolean + + Boolean + + + None + diff --git a/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md b/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md index 1edae9e..60ca28f 100644 --- a/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md +++ b/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md @@ -14,7 +14,8 @@ Create intunewin file from Winget installer ``` New-WtWingetPackage [-PackageId] [[-PackageFolder] ] [[-Version] ] - [[-TempFolder] ] [-ProgressAction ] [] + [[-TempFolder] ] [-Architecture ] [-InstallerContext ] + [-PackageScript ] [-ProgressAction ] [] ``` ## DESCRIPTION @@ -106,6 +107,51 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Architecture +Pick this architecture + +```yaml +Type: Architecture +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -InstallerContext +The installer context + +```yaml +Type: InstallerContext +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -PackageScript +Package WinGet script, instead of the actual installer. Helpful for installers that don't really work with WinTuner. + +```yaml +Type: Boolean +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/src/WingetIntune.Cli/Commands/PackageCommand.cs b/src/WingetIntune.Cli/Commands/PackageCommand.cs index ea7b36b..4d5da1c 100644 --- a/src/WingetIntune.Cli/Commands/PackageCommand.cs +++ b/src/WingetIntune.Cli/Commands/PackageCommand.cs @@ -30,7 +30,12 @@ internal class PackageCommand : Command IsHidden = isHidden, }; - internal static Option GetInstallerContextOption(bool isHidden = false) => new Option("--installer-context", () => InstallerContext.User, "Installer context to use") + internal static Option GetInstallerContextOption(bool isHidden = false) => new Option("--installer-context", () => InstallerContext.System, "Installer context to use") + { + IsHidden = isHidden, + }; + + internal static Option GetPackageAsScriptOption(bool isHidden = false) => new Option("--package-script", () => null, "Package just a winget script, not the installer itself") { IsHidden = isHidden, }; @@ -51,6 +56,7 @@ public PackageCommand() : base(name, description) AddOption(GetPackageFolderOption(isRequired: true)); AddOption(GetArchitectureOption(isHidden: false)); AddOption(GetInstallerContextOption(isHidden: false)); + AddOption(GetPackageAsScriptOption(isHidden: false)); AddOption(UseWingetOption); this.Handler = CommandHandler.Create(HandleCommand); } @@ -88,7 +94,7 @@ private async Task HandleCommand(PackageCommandOptions options, InvocationC await intuneManager.GenerateInstallerPackage(options.TempFolder, options.PackageFolder!, packageInfo, - new PackageOptions { Architecture = options.Architecture, InstallerContext = options.InstallerContext }, + new PackageOptions { Architecture = options.Architecture, InstallerContext = options.InstallerContext, PackageScript = options.PackageScript == true }, cancellationToken); return 0; @@ -105,4 +111,5 @@ internal class PackageCommandOptions : WinGetRootCommand.DefaultOptions public bool UseWinget { get; set; } public InstallerContext InstallerContext { get; set; } public Architecture Architecture { get; set; } + public bool? PackageScript { get; set; } } diff --git a/src/WingetIntune.Cli/Commands/PublishCommand.cs b/src/WingetIntune.Cli/Commands/PublishCommand.cs index 7af7838..6b6fa9e 100644 --- a/src/WingetIntune.Cli/Commands/PublishCommand.cs +++ b/src/WingetIntune.Cli/Commands/PublishCommand.cs @@ -42,6 +42,7 @@ public PublishCommand() : base(name, description) AddOption(new Option("--auto-update", "Turn on auto update, if assigned as available") { IsHidden = true }); AddOption(PackageCommand.GetArchitectureOption(isHidden: true)); AddOption(PackageCommand.GetInstallerContextOption(isHidden: true)); + AddOption(PackageCommand.GetPackageAsScriptOption(isHidden: true)); this.Handler = CommandHandler.Create(HandleCommand); } @@ -78,7 +79,7 @@ private async Task HandleCommand(PublishCommandOptions options, InvocationC await intuneManager.GenerateInstallerPackage(options.TempFolder, options.PackageFolder!, tempInfo, - new PackageOptions { Architecture = options.Architecture, InstallerContext = options.InstallerContext }, + new PackageOptions { Architecture = options.Architecture, InstallerContext = options.InstallerContext, PackageScript = options.PackageScript == true }, cancellationToken); } packageInfo = tempInfo.Source == PackageSource.Store || options.AutoPackage @@ -130,4 +131,5 @@ internal class PublishCommandOptions : WinGetRootCommand.DefaultOptions public string TempFolder { get; set; } public InstallerContext InstallerContext { get; set; } public Architecture Architecture { get; set; } + public bool? PackageScript { get; set; } } diff --git a/src/WingetIntune/Intune/IntuneManager.cs b/src/WingetIntune/Intune/IntuneManager.cs index 7c72065..b6b1840 100644 --- a/src/WingetIntune/Intune/IntuneManager.cs +++ b/src/WingetIntune/Intune/IntuneManager.cs @@ -73,6 +73,16 @@ public IntuneManager(ILoggerFactory? loggerFactory, IFileManager fileManager, IP return new Models.WingetPackage(packageInfo, packageFolder, intunePackage!); } + /// + /// Generates an Intune package for a winget package + /// + /// Folder to temporary store files + /// Folder where the package should be + /// (Partial) information about the package + /// User-defined options + /// Cancellation token + /// + /// public async Task GenerateInstallerPackage(string tempFolder, string outputFolder, Models.PackageInfo packageInfo, PackageOptions? packageOptions = null, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrEmpty(tempFolder); @@ -81,17 +91,20 @@ public IntuneManager(ILoggerFactory? loggerFactory, IFileManager fileManager, IP { packageOptions = PackageOptions.Create(); } + if (packageInfo.Source != PackageSource.Winget) { throw new ArgumentException("Package is not a winget package", nameof(packageInfo)); } + if (!packageInfo.InstallersLoaded) { packageInfo = await wingetRepository.GetPackageInfoAsync(packageInfo.PackageIdentifier!, packageInfo.Version, "winget", cancellationToken); } ComputeInstallerDetails(ref packageInfo, packageOptions); - if (packageInfo.InstallerType.IsMsi()) + + if (packageInfo.InstallerType.IsMsi() && !packageOptions.PackageScript) { return await GenerateMsiPackage(tempFolder, outputFolder, packageInfo, packageOptions, cancellationToken); } @@ -106,34 +119,51 @@ private async Task GenerateNoneMsiInstaller(string tempFolder, st ArgumentNullException.ThrowIfNull(packageInfo); var packageTempFolder = fileManager.CreateFolderForPackage(tempFolder, packageInfo.PackageIdentifier!, packageInfo.Version!); var packageFolder = fileManager.CreateFolderForPackage(outputFolder, packageInfo.PackageIdentifier!, packageInfo.Version!); - // TODO : If installer is not supported (yet) should it be downloaded? - if (SupportedInstallers.Contains(packageInfo.InstallerType)) + + if (SupportedInstallers.Contains(packageInfo.InstallerType) && packageOptions.PackageScript != true) { - var installerPath = await DownloadInstallerAsync(packageTempFolder, packageInfo, cancellationToken); + _ = await DownloadInstallerAsync(packageTempFolder, packageInfo, cancellationToken); } else { // Generate scripts if (packageInfo.InstallCommandLine!.StartsWith("winget")) { - // TODO Create Winget Install script + // WinGet is not always available in the context of the user and you need to make sure to run to correct version. + // This is way there is a helper script that always discovers the correct winget location to use. + var installScript = GetPsCommandContent(packageInfo.InstallCommandLine, "installed", $"Package {packageInfo.PackageIdentifier} v{packageInfo.Version} installed successfully", packageId: packageInfo.PackageIdentifier, action: "install"); await fileManager.WriteAllTextAsync( Path.Combine(packageTempFolder, "install.ps1"), - GetPsCommandContent(packageInfo.InstallCommandLine, "installed", $"Package {packageInfo.PackageIdentifier} v{packageInfo.Version} installed successfully", packageId: packageInfo.PackageIdentifier, action: "install"), + installScript, cancellationToken); packageInfo.InstallCommandLine = $"%windir%\\sysnative\\windowspowershell\\v1.0\\powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File install.ps1"; packageInfo.InstallerFilename = "install.ps1"; + + // Also output the script to the output folder + await fileManager.WriteAllTextAsync( + Path.Combine(packageFolder, "install.ps1"), + installScript, + cancellationToken); } } if (packageInfo.UninstallCommandLine!.StartsWith("winget")) { + // Helper script to discover the correct winget location + var uninstallScript = GetPsCommandContent(packageInfo.UninstallCommandLine, "uninstalled", $"Package {packageInfo.PackageIdentifier} uninstalled successfully", packageId: packageInfo.PackageIdentifier, action: "uninstall"); await fileManager.WriteAllTextAsync( Path.Combine(packageTempFolder, "uninstall.ps1"), - GetPsCommandContent(packageInfo.UninstallCommandLine, "uninstalled", $"Package {packageInfo.PackageIdentifier} uninstalled successfully", packageId: packageInfo.PackageIdentifier, action: "uninstall"), + uninstallScript, cancellationToken); packageInfo.UninstallCommandLine = $"%windir%\\sysnative\\windowspowershell\\v1.0\\powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -File uninstall.ps1"; + + // Also output the script to the output folder + await fileManager.WriteAllTextAsync( + Path.Combine(packageFolder, "uninstall.ps1"), + uninstallScript, + cancellationToken); } + var intuneFile = await intunePackager.CreatePackage(packageTempFolder, packageFolder, packageInfo.InstallerFilename!, packageInfo, cancellationToken); await DownloadLogoAsync(packageFolder, packageInfo.PackageIdentifier!, cancellationToken); @@ -506,7 +536,7 @@ private void ComputeInstallerDetails(ref PackageInfo package, PackageOptions pac package.InstallerContext = installer.ParseInstallerContext() == InstallerContext.Unknown ? (package.InstallerContext ?? packageOptions.InstallerContext) : installer.ParseInstallerContext(); package.InstallerType = installer.ParseInstallerType(); package.Installer = installer; - if (!package.InstallerType.IsMsi()) + if (!package.InstallerType.IsMsi() || packageOptions.PackageScript == true) { ComputeInstallerCommands(ref package, packageOptions); } @@ -517,19 +547,6 @@ private void ComputeInstallerDetails(ref PackageInfo package, PackageOptions pac } private static readonly InstallerType[] SupportedInstallers = new[] { InstallerType.Inno, InstallerType.Msi, InstallerType.Burn, InstallerType.Wix, InstallerType.Nullsoft, InstallerType.Exe }; - private static string GuessInstallerName(InstallerType installerType) => installerType switch - { - InstallerType.Inno => "setup.exe", - InstallerType.Msi => "setup.msi", - InstallerType.Msix => "setup.msix", - InstallerType.Appx => "setup.appx", - InstallerType.Burn => "setup.exe", - InstallerType.Wix => "setup.msi", - InstallerType.Nullsoft => "setup.exe", - InstallerType.Exe => "setup.exe", - InstallerType.Zip => "setup.zip", - _ => throw new ArgumentException("Unknown installer type", nameof(installerType)) - }; private static string GuessInstallerExtension(InstallerType installerType) => installerType switch { @@ -551,42 +568,52 @@ private void ComputeInstallerDetails(ref PackageInfo package, PackageOptions pac { InstallerType.Nullsoft, "/S" }, }; + /// + /// Compute the installer commands for the package + /// + /// Package info + /// User-defined options private void ComputeInstallerCommands(ref PackageInfo package, PackageOptions packageOptions) { - // TODO: Add support for other installer types and adjust `SupportedInstallers` accordingly - - string? installerSwitches = package.Installer?.InstallerSwitches?.GetPreferred(); - switch (package.InstallerType) - { - case InstallerType.Inno: - if (installerSwitches?.Contains("/VERYSILENT") != true) - { - installerSwitches += " " + DefaultInstallerSwitches[InstallerType.Inno]; - installerSwitches = installerSwitches.Trim(); - } - package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Inno]}"; - // Don't know the uninstall command - // Configure the uninstall command for Inno Setup - //package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /D={{0}}"; - break; - - case InstallerType.Burn: - package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Burn]}"; - // Have to check the uninstall command - package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /quiet /norestart /uninstall /passive"; // /burn.ignoredependencies=\"{package.PackageIdentifier}\" - break; - - case InstallerType.Nullsoft: - package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Nullsoft]}"; - break; - - case InstallerType.Exe: - package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches}"; - // Have to check the uninstall command - //package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /quiet /norestart /uninstall /passive"; // /burn.ignoredependencies=\"{package.PackageIdentifier}\" - break; + // If package script is enabled, we will just use the winget install & uninstall commands + // This way your packages in Intune will not contain the installer files + // And it also helps with installers that otherwise would just not install silently or install at all + if (packageOptions.PackageScript != true) + { + string? installerSwitches = package.Installer?.InstallerSwitches?.GetPreferred(); + switch (package.InstallerType) + { + case InstallerType.Inno: + if (installerSwitches?.Contains("/VERYSILENT") != true) + { + installerSwitches += " " + DefaultInstallerSwitches[InstallerType.Inno]; + installerSwitches = installerSwitches.Trim(); + } + package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Inno]}"; + // Don't know the uninstall command + // Configure the uninstall command for Inno Setup + //package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /D={{0}}"; + break; + + case InstallerType.Burn: + package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Burn]}"; + // Have to check the uninstall command + package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /quiet /norestart /uninstall /passive"; // /burn.ignoredependencies=\"{package.PackageIdentifier}\" + break; + + case InstallerType.Nullsoft: + package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches ?? DefaultInstallerSwitches[InstallerType.Nullsoft]}"; + break; + + case InstallerType.Exe: + package.InstallCommandLine = $"\"{package.InstallerFilename}\" {installerSwitches}"; + // Have to check the uninstall command + //package.UninstallCommandLine = $"\"{package.InstallerFilename}\" /quiet /norestart /uninstall /passive"; // /burn.ignoredependencies=\"{package.PackageIdentifier}\" + break; + } } + // If the installer type is unsupported or the package script is enabled, we will generate a script to install the package if (string.IsNullOrWhiteSpace(package.InstallCommandLine)) { var installArguments = WingetHelper.GetInstallArgumentsForPackage(package.PackageIdentifier!, package.Version, installerContext: package.InstallerContext ?? InstallerContext.Unknown); @@ -594,10 +621,10 @@ private void ComputeInstallerCommands(ref PackageInfo package, PackageOptions pa package.InstallCommandLine = $"winget {installArguments}"; } + // Uninstall command is almost always empty, so we just use winget to uninstall the package if (string.IsNullOrWhiteSpace(package.UninstallCommandLine)) { var uninstallArguments = WingetHelper.GetUninstallArgumentsForPackage(package.PackageIdentifier!, installerContext: package.InstallerContext ?? InstallerContext.Unknown); - // This seems like a hack I know, but it's the only way to get the uninstall command for now. package.UninstallCommandLine = $"winget {uninstallArguments}"; } } @@ -605,14 +632,14 @@ private void ComputeInstallerCommands(ref PackageInfo package, PackageOptions pa private async Task WriteReadmeAsync(string packageFolder, PackageInfo packageInfo, CancellationToken cancellationToken) { var sb = new StringBuilder(); - if (packageInfo.InstallerType.IsMsi()) + if (packageInfo.InstallerType.IsMsi() || !string.IsNullOrEmpty(packageInfo.MsiProductCode)) { - logger.LogInformation("Writing detection info for msi package {packageId} {productCode}", packageInfo.PackageIdentifier, packageInfo.MsiProductCode!); + logger.LogInformation("Writing detection info with msi details {packageId} {productCode}", packageInfo.PackageIdentifier, packageInfo.MsiProductCode!); sb.AppendFormat("Package {0} {1} from {2}\r\n", packageInfo.PackageIdentifier, packageInfo.Version, packageInfo.Source); sb.AppendLine(); sb.AppendFormat("MsiProductCode={0}\r\n", packageInfo.MsiProductCode); - sb.AppendFormat("MsiVersion={0}\r\n", packageInfo.MsiVersion!); + sb.AppendFormat("MsiVersion={0}\r\n", packageInfo.MsiVersion); var detectionFile = Path.Combine(packageFolder, "detection.txt"); await fileManager.WriteAllTextAsync(detectionFile, sb.ToString(), cancellationToken); @@ -637,11 +664,12 @@ private async Task WriteReadmeAsync(string packageFolder, PackageInfo packageInf } sb.AppendLine(); sb.AppendLine("Uninstall script:"); - if (packageInfo.InstallerType.IsMsi()) + if (packageInfo.InstallerType.IsMsi() || !string.IsNullOrEmpty(packageInfo.MsiProductCode)) { - sb.AppendFormat("msiexec /x {0} /quiet /qn\r\n", packageInfo.MsiProductCode); + sb.AppendFormat("msiexec /x {0} /quiet /qn\r\nor\r\n", packageInfo.MsiProductCode); } - else + + if (!string.IsNullOrEmpty(packageInfo.UninstallCommandLine)) { sb.AppendFormat("{0}\r\n", packageInfo.UninstallCommandLine); } diff --git a/src/WingetIntune/Intune/IntuneManagerConstants.cs b/src/WingetIntune/Intune/IntuneManagerConstants.cs index 16db45f..15161c2 100644 --- a/src/WingetIntune/Intune/IntuneManagerConstants.cs +++ b/src/WingetIntune/Intune/IntuneManagerConstants.cs @@ -44,7 +44,7 @@ Exit 1 Exit 0 } } -Write-Host ""Command Unsuccesful"" +Write-Host ""Command Unsuccessful"" Write-Host ""$procOutput"" Exit 5 "; diff --git a/src/WingetIntune/Models/IntuneApp.cs b/src/WingetIntune/Models/IntuneApp.cs index f9d3fbb..5a1f3b8 100644 --- a/src/WingetIntune/Models/IntuneApp.cs +++ b/src/WingetIntune/Models/IntuneApp.cs @@ -31,4 +31,20 @@ public class IntuneApp /// public int? SupersedingAppCount { get; set; } + /// + /// The installer context + /// + /// + /// You should probably not update an app with a different installer context + /// + public InstallerContext InstallerContext { get; set; } + + /// + /// The architecture of the app + /// + /// + /// You should probably not update an app with a different architecture + /// + public Architecture Architecture { get; set; } + } diff --git a/src/WingetIntune/Models/Mapper.cs b/src/WingetIntune/Models/Mapper.cs index f6fa783..cc41332 100644 --- a/src/WingetIntune/Models/Mapper.cs +++ b/src/WingetIntune/Models/Mapper.cs @@ -50,6 +50,7 @@ public Win32LobApp ToWin32LobApp(PackageInfo packageInfo) if (packageInfo.MsiProductCode is not null && packageInfo.MsiVersion is not null) // packageInfo.InstallerType.IsMsi() { + // Not sure if this is correct, should this information always be set if available? if (packageInfo.InstallerType.IsMsi()) { app.MsiInformation = new Win32LobAppMsiInformation @@ -61,6 +62,8 @@ public Win32LobApp ToWin32LobApp(PackageInfo packageInfo) }; } + // Using ProductCode detection, is much faster then the detection script + // if we have this information, we should use it. app.DetectionRules = new List { new Win32LobAppProductCodeDetection @@ -79,10 +82,12 @@ public Win32LobApp ToWin32LobApp(PackageInfo packageInfo) { ScriptContent = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(packageInfo.DetectionScript!)), EnforceSignatureCheck = false, + // Not sure if winget is even available on 32 bit systems RunAs32Bit = packageInfo.Architecture == Architecture.X86 } }; } + app.Developer = packageInfo.Publisher; app.FileName = Path.GetFileNameWithoutExtension(packageInfo.InstallerFilename) + ".intunewin"; app.SetupFilePath = packageInfo.InstallerFilename ?? "install.ps1"; @@ -159,7 +164,15 @@ internal static IntuneApp ToIntuneApp(Win32LobApp? win32LobApp) CurrentVersion = win32LobApp.DisplayVersion!, GraphId = win32LobApp.Id!, SupersededAppCount = win32LobApp.SupersededAppCount, - SupersedingAppCount = win32LobApp.SupersedingAppCount + SupersedingAppCount = win32LobApp.SupersedingAppCount, + InstallerContext = win32LobApp.InstallExperience?.RunAsAccount == RunAsAccountType.User ? InstallerContext.User : InstallerContext.System, + Architecture = win32LobApp.ApplicableArchitectures switch + { + WindowsArchitecture.Arm64 => Architecture.Arm64, + WindowsArchitecture.X64 => Architecture.X64, + WindowsArchitecture.X86 => Architecture.X86, + _ => Architecture.Neutral + } }; } } diff --git a/src/WingetIntune/Models/PackageOptions.cs b/src/WingetIntune/Models/PackageOptions.cs index 88b4a41..78d2125 100644 --- a/src/WingetIntune/Models/PackageOptions.cs +++ b/src/WingetIntune/Models/PackageOptions.cs @@ -4,6 +4,7 @@ public class PackageOptions { public InstallerContext InstallerContext { get; init; } public Architecture Architecture { get; init; } + public bool PackageScript { get; init; } - public static PackageOptions Create() => new PackageOptions { Architecture = Architecture.X64, InstallerContext = InstallerContext.User }; + public static PackageOptions Create() => new PackageOptions { Architecture = Architecture.X64, InstallerContext = InstallerContext.System, PackageScript = false }; }