From 539649b2c43f2d01ecf64493b2074da9d3e9b746 Mon Sep 17 00:00:00 2001 From: Stephan van Rooij <1292510+svrooij@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:33:17 +0200 Subject: [PATCH 1/4] Test package in Windows Sandbox --- .../Commands/TestWtWin32App.cs | 175 ++++++++++++++ src/WingetIntune/Testing/WindowsSandbox.cs | 223 ++++++++++++++++++ .../WingetServiceCollectionExtension.cs | 2 + 3 files changed, 400 insertions(+) create mode 100644 src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs create mode 100644 src/WingetIntune/Testing/WindowsSandbox.cs diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs new file mode 100644 index 0000000..f64003f --- /dev/null +++ b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs @@ -0,0 +1,175 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Graph.Beta; +using Svrooij.PowerShell.DependencyInjection; +using System; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using WingetIntune; +using WingetIntune.Graph; +using WingetIntune.Intune; +using WingetIntune.Models; +using WingetIntune.Testing; +using GraphModels = Microsoft.Graph.Beta.Models; + +namespace Svrooij.WinTuner.CmdLets.Commands; +/// +/// Test if a package will install +/// Test if a package will install on the Windows Sandbox +/// Documentation +/// +/// +/// Test a packaged installer in sandbox +/// Test-WtWin32App -PackageFolder D:\packages\JanDeDobbeleer.OhMyPosh\22.0.3 +/// +[Cmdlet(VerbsDiagnostic.Test, "WtWin32App", DefaultParameterSetName = nameof(PackageFolder))] +[OutputType(typeof(string))] +public class TestWtWin32App : DependencyCmdlet +{ + private const string ParameterSetApp = "Win32LobApp"; + private const string ParameterSetWinGet = "WinGet"; + /// + /// The Win32LobApp configuration you want to create + /// + [Parameter( + Mandatory = true, + Position = 0, + ParameterSetName = ParameterSetApp, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = false, + HelpMessage = "The App configuration you want to create")] + public GraphModels.Win32LobApp? App { get; set; } + + /// + /// The package id to upload to Intune. + /// + [Parameter( + Mandatory = true, + Position = 0, + ParameterSetName = ParameterSetWinGet, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = false, + HelpMessage = "The package id to upload to Intune.")] + public string? PackageId { get; set; } + + /// + /// The version to upload to Intune + /// + [Parameter( + Mandatory = true, + Position = 1, + ParameterSetName = ParameterSetWinGet, + ValueFromPipeline = false, + HelpMessage = "The version to upload to Intune" + )] + public string? Version { get; set; } + + /// + /// The Root folder where all the package live in. + /// + [Parameter( + Mandatory = true, + Position = 2, + ParameterSetName = ParameterSetWinGet, + ValueFromPipeline = false, + HelpMessage = "The Root folder where all the package live in.")] + public string? RootPackageFolder { get; set; } + + /// + /// The folder where the package is + /// + [Parameter( + Mandatory = true, + Position = 0, + ParameterSetName = nameof(PackageFolder), + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The folder where the package is")] + public string? PackageFolder { get; set; } + + /// + /// Clean the test files after run + /// + [Parameter( + Mandatory = false, + HelpMessage = "Clean the test files after run")] + public SwitchParameter Clean { get; set; } + + /// + /// Sleep for x seconds before closing + /// + [Parameter( + Mandatory = false, + HelpMessage = "Sleep for x seconds before auto shutdown")] + public int? Sleep { get; set; } + + [ServiceDependency] + private ILogger? logger; + + [ServiceDependency] + private WindowsSandbox? sandbox; + + [ServiceDependency] + private MetadataManager? metadataManager; + + /// + public override async Task ProcessRecordAsync(CancellationToken cancellationToken) + { + string? IntuneWinFile = null; + PackageInfo? packageInfo = null; + if (App is null) + { + if (ParameterSetName == ParameterSetWinGet) + { + logger?.LogDebug("Loading package details from RootPackageFolder {RootPackageFolder}, PackageId {PackageId}, Version {Version}", RootPackageFolder, PackageId, Version); + PackageFolder = Path.Combine(RootPackageFolder!, PackageId!, Version!); + logger?.LogDebug("Loading package details from folder {packageFolder}", PackageFolder); + } + + if (PackageFolder is not null) + { + logger?.LogInformation("Loading package details from folder {packageFolder}", PackageFolder); + packageInfo = await metadataManager!.LoadPackageInfoFromFolderAsync(PackageFolder, cancellationToken); + App = metadataManager.ConvertPackageInfoToWin32App(packageInfo); + IntuneWinFile = metadataManager.GetIntuneWinFileName(PackageFolder, packageInfo); + } + else + { + var ex = new ArgumentException("PackageFolder was provided"); + logger?.LogError(ex, "PackageFolder was provided"); + throw ex; + } + } + + + + var outputFolder = Path.Combine(Path.GetTempPath(), "wintuner-sandbox", Guid.NewGuid().ToString()); + + var sandboxFile = await sandbox!.PrepareSandboxFileForPackage(packageInfo!, IntuneWinFile!, outputFolder, timeout: Sleep, cancellationToken: cancellationToken); + logger?.LogDebug("Sandbox file created at {sandboxFile}", sandboxFile); + var result = await sandbox.RunSandbox(sandboxFile, Clean, cancellationToken); + if (result is null) + { + logger?.LogError("Sandbox exited with null result"); + return; + } + + logger?.LogInformation("Installed {PackageId} {Version} in sandbox, reported exitcode {ExitCode}, number of apps installed {AppsInstalled}", packageInfo!.PackageIdentifier, packageInfo.Version, result.ExitCode, result?.InstalledApps?.Count()); + logger?.LogInformation("Sandbox result: {Result}", result); + //if(Open) + //{ + + // var result = await processManager.RunProcessAsync("WindowsSandbox.exe", sandboxFile, cancellationToken); + // logger?.LogInformation("Sandbox exited, wait a bit and cleanup"); + // await Task.Delay(3000); + // Directory.Delete(outputFolder, true); + //} else + //{ + // logger?.LogInformation("Sandbox file created at {sandboxFile}", sandboxFile); + // WriteObject(sandboxFile); + //} + } +} diff --git a/src/WingetIntune/Testing/WindowsSandbox.cs b/src/WingetIntune/Testing/WindowsSandbox.cs new file mode 100644 index 0000000..db9e195 --- /dev/null +++ b/src/WingetIntune/Testing/WindowsSandbox.cs @@ -0,0 +1,223 @@ +using Microsoft.Extensions.Logging; +using SvRooij.ContentPrep; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using WingetIntune.Models; + +namespace WingetIntune.Testing; +public class WindowsSandbox +{ + private readonly ILogger logger; + private readonly Packager packager; + private readonly IProcessManager processManager; + + public WindowsSandbox(ILoggerFactory loggerFactory, IProcessManager processManager) + { + this.logger = loggerFactory.CreateLogger(); + this.packager = new Packager(loggerFactory.CreateLogger()); + this.processManager = processManager; + } + + public async Task PrepareSandboxFileForPackage(PackageInfo packageInfo, string intuneWinFile, string outputFolder, int? timeout = null, CancellationToken cancellationToken = default) + { + logger.LogInformation("Preparing sandbox file for {PackageId} {Version}", packageInfo.PackageIdentifier, packageInfo.Version); + + var installerFolder = Path.Combine(outputFolder, "installer"); + var logsFolder = Path.Combine(outputFolder, "logs"); + + Directory.CreateDirectory(installerFolder); + Directory.CreateDirectory(logsFolder); + await packager.Unpack(intuneWinFile, installerFolder, cancellationToken); + + var sandboxFile = Path.Combine(outputFolder, "sandbox.wsb"); + await WriteSandboxConfig(sandboxFile, installerFolder, logsFolder); + await WriteTestScript(installerFolder, packageInfo, timeout); + + return sandboxFile; + } + + private static async Task WriteSandboxConfig(string sandboxFilename, string installerFolder, string logFolder) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(""); + // Mapped folders (installer and logs) + // some logs are parsed to show the actual result. + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine($" {installerFolder}"); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\installer"); + stringBuilder.AppendLine(" true"); + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine($" {logFolder}"); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Desktop\\logs"); + stringBuilder.AppendLine(" false"); + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine(" "); + + // Startup command + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\wintuner\\startup.cmd"); + stringBuilder.AppendLine(" "); + + // Security settings + stringBuilder.AppendLine(" Disable"); + stringBuilder.AppendLine(" Disable"); + // Not sure about this next one https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-configure-using-wsb-file#protected-client + stringBuilder.AppendLine(" Enabled"); + stringBuilder.AppendLine(" Disable"); + stringBuilder.AppendLine(" Disable"); + stringBuilder.AppendLine(""); + + await File.WriteAllTextAsync(sandboxFilename, stringBuilder.ToString()); + } + + private static async Task WriteTestScript(string installerFolder, PackageInfo packageInfo, int? timeout) + { + var arguments = packageInfo.InstallCommandLine?.Replace($"\"{packageInfo.InstallerFilename}\" ", ""); + // Create a batch script that will run a powershell script (Execution policy stuff...) + var sb = new StringBuilder(); + sb.AppendLine("@echo off"); + sb.AppendLine("start /wait /low powershell.exe -ExecutionPolicy Bypass -File \"C:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\wintuner\\install.ps1\""); + sb.AppendLine(); + Directory.CreateDirectory(Path.Combine(installerFolder, "wintuner")); + await File.WriteAllTextAsync(Path.Combine(installerFolder, "wintuner", "startup.cmd"), sb.ToString()); + + sb.Clear(); + + // Create the powershell script that will install the app + // and collect the installed apps + // This script will also shutdown the sandbox after the installation (if a timeout above -1 is provided) + + sb.AppendLine("Start-Transcript -Path c:\\Users\\WDAGUtilityAccount\\Desktop\\logs\\wintuner.log -Append -Force"); + sb.AppendLine("Write-Host \"Starting installation\""); + sb.AppendLine($"Write-Host \"Installer: {packageInfo.InstallerFilename}\""); + sb.AppendLine($"Write-Host \"Arguments: {arguments}\""); + + // execute the installer and capture the exit code in powershell + sb.AppendLine($"& c:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\{packageInfo.InstallerFilename} {arguments}"); + //sb.AppendLine("& cmd exit /b 5"); // This is a dummy command to test the exit code + sb.AppendLine("$exitCode = $LASTEXITCODE"); + sb.AppendLine("Write-Host \"Installer finished with exitcode $exitCode\""); + + // write the exit code to a file + sb.AppendLine("$exitCode | Out-File -FilePath c:\\Users\\WDAGUtilityAccount\\Desktop\\logs\\exitcode.txt"); + sb.AppendLine("Write-Host \"App installed, collecting installed apps\""); + sb.AppendLine("$apps = $(Get-WmiObject -Class Win32_InstalledWin32Program | Select-Object -Property Version,Vendor,Name)"); + sb.AppendLine("$apps | Format-Table -AutoSize"); + sb.AppendLine("$apps | Export-Csv -Path c:\\Users\\WDAGUtilityAccount\\Desktop\\logs\\installed.csv -NoTypeInformation"); + sb.AppendLine("Write-Host \"Installed apps collected\""); + sb.AppendLine("Stop-Transcript"); + if (timeout is not null && timeout >= 0) + { + if (timeout > 0) // Cancelable shutdown + { + sb.AppendLine($"shutdown /s /t {timeout}"); + sb.AppendLine($"Write-Host \"Closing sandbox in {timeout} seconds unless you press a button\""); + sb.AppendLine("Read-Host"); + sb.AppendLine("shutdown /a"); + } + else // Immediate shutdown + { + sb.AppendLine($"shutdown /s /t {timeout}"); + } + } + // Exit with the exit code of the installer (not sure if that does anything) + sb.AppendLine("exit $exitCode"); + await File.WriteAllTextAsync(Path.Combine(installerFolder, "wintuner", "install.ps1"), sb.ToString()); + } + + public async Task RunSandbox(string sandboxFile, bool cleanup, CancellationToken cancellationToken) + { + logger.LogInformation("Running sandbox {sandboxFile}", sandboxFile); + var processResult = await processManager.RunProcessAsync("WindowsSandbox.exe", sandboxFile, cancellationToken); + logger.LogInformation("Sandbox exited with exitcode {exitCode}", processResult.ExitCode); + bool shouldProcess = true; + SandboxResult? result = null; + if (processResult.ExitCode == -2147024713) + { + logger.LogWarning("Sandbox failed to start, this is likely because the host does not support virtualization or already started"); + shouldProcess = false; + } + await Task.Delay(1500, cancellationToken); + + if (shouldProcess) + { + var logDirectory = Path.Combine(Path.GetDirectoryName(sandboxFile)!, "logs"); + var logFile = Path.Combine(logDirectory, "wintuner.log"); + var exitCodeFile = Path.Combine(logDirectory, "exitcode.txt"); + result = new SandboxResult + { + ExitCode = File.Exists(exitCodeFile) && int.TryParse(await File.ReadAllTextAsync(exitCodeFile), out int exitCode) ? exitCode : 0, + Log = File.Exists(logFile) ? await File.ReadAllTextAsync(logFile) : null, + InstalledApps = await ParseInstalledApps(Path.Combine(logDirectory, "installed.csv")) + }; + } + + if (cleanup) + { + logger.LogDebug("Cleaning up sandbox files"); + Directory.Delete(Path.GetDirectoryName(sandboxFile)!, true); + } + return result; + + } + + private async Task?> ParseInstalledApps(string filename) + { + + // the file is a csv with headers Version,Vendor,Name and uses , as separator + // Parse the file if it exists + if (!File.Exists(filename)) + { + logger.LogWarning("Installed apps file not found {filename}", filename); + return null; + } + + var lines = await File.ReadAllLinesAsync(filename); + if (lines.Length < 2) + { + logger.LogWarning("Installed apps file is empty {filename}", filename); + return null; + } + + return lines.Skip(1).Select(l => + { + var parts = l.Split(','); + return new SandboxInstalledApps + { + Version = parts[0].Trim('"'), + Vendor = parts[1].Trim('"'), + Name = parts[2].Trim('"') + }; + }); + } + + public class SandboxResult + { + public int ExitCode { get; set; } = 0; + public string? Log { get; set; } + public IEnumerable? InstalledApps { get; set; } + + public override string ToString() + { + return InstalledApps?.Count() > 0 ? $"ExitCode: {ExitCode}, Installed apps {string.Join(", ", InstalledApps.Select(i => i.Name))}" : $"Exit code: {ExitCode}"; + } + + } + + public class SandboxInstalledApps + { + public string? Version { get; set; } + public string? Vendor { get; set; } + public string? Name { get; set; } + + public override string ToString() + { + return $"{Name} by {Vendor}"; + } + } +} diff --git a/src/WingetIntune/WingetServiceCollectionExtension.cs b/src/WingetIntune/WingetServiceCollectionExtension.cs index e5fc982..a68fd74 100644 --- a/src/WingetIntune/WingetServiceCollectionExtension.cs +++ b/src/WingetIntune/WingetServiceCollectionExtension.cs @@ -3,6 +3,7 @@ using Microsoft.Kiota.Http.HttpClientLibrary; using WingetIntune.Interfaces; using WingetIntune.Intune; +using WingetIntune.Testing; using WinTuner.Proxy.Client; [assembly: InternalsVisibleTo("WingetIntune.Tests")] namespace WingetIntune; @@ -54,6 +55,7 @@ public static IServiceCollection AddWingetServices(this IServiceCollection servi services.AddTransient(); services.AddSingleton(); services.AddSingleton(); + services.AddTransient(); services.AddWinTunerProxyClient(config => { From b1b721eed20ce18925b51dee4ae71f8b859abcc5 Mon Sep 17 00:00:00 2001 From: Stephan van Rooij <1292510+svrooij@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:33:59 +0200 Subject: [PATCH 2/4] More testing in sandbox --- .../{TestWtWin32App.cs => TestWtIntuneWin.cs} | 127 ++++++++------- .../Commands/TestWtSetupFile.cs | 88 +++++++++++ src/WingetIntune/Intune/MetadataManager.cs | 33 ++++ src/WingetIntune/Testing/WindowsSandbox.cs | 148 +++++++++++++++--- 4 files changed, 317 insertions(+), 79 deletions(-) rename src/Svrooij.WinTuner.CmdLets/Commands/{TestWtWin32App.cs => TestWtIntuneWin.cs} (54%) create mode 100644 src/Svrooij.WinTuner.CmdLets/Commands/TestWtSetupFile.cs diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs similarity index 54% rename from src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs rename to src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs index f64003f..fd17bcd 100644 --- a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtWin32App.cs +++ b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs @@ -19,29 +19,18 @@ namespace Svrooij.WinTuner.CmdLets.Commands; /// /// Test if a package will install /// Test if a package will install on the Windows Sandbox -/// Documentation +/// Documentation /// /// /// Test a packaged installer in sandbox -/// Test-WtWin32App -PackageFolder D:\packages\JanDeDobbeleer.OhMyPosh\22.0.3 +/// Test-WtIntuneWin -PackageFolder D:\packages\JanDeDobbeleer.OhMyPosh\22.0.3 /// -[Cmdlet(VerbsDiagnostic.Test, "WtWin32App", DefaultParameterSetName = nameof(PackageFolder))] +[Cmdlet(VerbsDiagnostic.Test, "WtIntuneWin", DefaultParameterSetName = nameof(PackageFolder))] [OutputType(typeof(string))] -public class TestWtWin32App : DependencyCmdlet +public class TestWtIntuneWin : DependencyCmdlet { - private const string ParameterSetApp = "Win32LobApp"; private const string ParameterSetWinGet = "WinGet"; - /// - /// The Win32LobApp configuration you want to create - /// - [Parameter( - Mandatory = true, - Position = 0, - ParameterSetName = ParameterSetApp, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = false, - HelpMessage = "The App configuration you want to create")] - public GraphModels.Win32LobApp? App { get; set; } + private const string ParameterSetIntuneWin = "IntuneWin"; /// /// The package id to upload to Intune. @@ -90,6 +79,50 @@ public class TestWtWin32App : DependencyCmdlet HelpMessage = "The folder where the package is")] public string? PackageFolder { get; set; } + /// + /// The IntuneWin file to test + /// + [Parameter( + Mandatory = true, + Position = 0, + ParameterSetName = ParameterSetIntuneWin, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The IntuneWin file to test")] + public string? IntuneWinFile { get; set; } + + /// + /// The installer filename (if not set correctly inside the intunewin) + /// + [Parameter( + Mandatory = false, + Position = 1, + ParameterSetName = ParameterSetIntuneWin, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The installer filename (if not set correctly inside the intunewin)")] + public string? InstallerFilename { get; set; } + + /// + /// The installer arguments (if you want it to execute silently) + /// + [Parameter( + Mandatory = false, + Position = 2, + ParameterSetName = ParameterSetIntuneWin, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "The installer arguments (if you want it to execute silently)")] + [Parameter( + Mandatory = false, + Position = 2, + ParameterSetName = nameof(PackageFolder), + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = false, + DontShow = true, + HelpMessage = "Override the installer arguments")] + public string? InstallerArguments { get; set; } + /// /// Clean the test files after run /// @@ -118,37 +151,31 @@ public class TestWtWin32App : DependencyCmdlet /// public override async Task ProcessRecordAsync(CancellationToken cancellationToken) { - string? IntuneWinFile = null; - PackageInfo? packageInfo = null; - if (App is null) + if (ParameterSetName == ParameterSetWinGet) { - if (ParameterSetName == ParameterSetWinGet) - { - logger?.LogDebug("Loading package details from RootPackageFolder {RootPackageFolder}, PackageId {PackageId}, Version {Version}", RootPackageFolder, PackageId, Version); - PackageFolder = Path.Combine(RootPackageFolder!, PackageId!, Version!); - logger?.LogDebug("Loading package details from folder {packageFolder}", PackageFolder); - } - - if (PackageFolder is not null) - { - logger?.LogInformation("Loading package details from folder {packageFolder}", PackageFolder); - packageInfo = await metadataManager!.LoadPackageInfoFromFolderAsync(PackageFolder, cancellationToken); - App = metadataManager.ConvertPackageInfoToWin32App(packageInfo); - IntuneWinFile = metadataManager.GetIntuneWinFileName(PackageFolder, packageInfo); - } - else - { - var ex = new ArgumentException("PackageFolder was provided"); - logger?.LogError(ex, "PackageFolder was provided"); - throw ex; - } + logger?.LogDebug("Loading package details from RootPackageFolder {RootPackageFolder}, PackageId {PackageId}, Version {Version}", RootPackageFolder, PackageId, Version); + PackageFolder = Path.Combine(RootPackageFolder!, PackageId!, Version!); + logger?.LogDebug("Loading package details from folder {packageFolder}", PackageFolder); } + if (PackageFolder is not null) + { + logger?.LogInformation("Loading package details from folder {packageFolder}", PackageFolder); + var packageInfo = await metadataManager!.LoadPackageInfoFromFolderAsync(PackageFolder, cancellationToken); + InstallerFilename = packageInfo.InstallerFilename; + // If the installer arguments are not set, use the ones from the package info. + InstallerArguments ??= packageInfo.InstallCommandLine?.Replace($"\"{packageInfo.InstallerFilename!}\" ", ""); + IntuneWinFile = metadataManager.GetIntuneWinFileName(PackageFolder, packageInfo); + } + + if (IntuneWinFile is null) + { + var ex = new ArgumentException("PackageFolder was provided"); + logger?.LogError(ex, "PackageFolder was provided"); + throw ex; + } - - var outputFolder = Path.Combine(Path.GetTempPath(), "wintuner-sandbox", Guid.NewGuid().ToString()); - - var sandboxFile = await sandbox!.PrepareSandboxFileForPackage(packageInfo!, IntuneWinFile!, outputFolder, timeout: Sleep, cancellationToken: cancellationToken); + var sandboxFile = await sandbox!.PrepareSandboxFileForPackage(IntuneWinFile!, InstallerFilename, InstallerArguments, timeout: Sleep, cancellationToken: cancellationToken); logger?.LogDebug("Sandbox file created at {sandboxFile}", sandboxFile); var result = await sandbox.RunSandbox(sandboxFile, Clean, cancellationToken); if (result is null) @@ -157,19 +184,7 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke return; } - logger?.LogInformation("Installed {PackageId} {Version} in sandbox, reported exitcode {ExitCode}, number of apps installed {AppsInstalled}", packageInfo!.PackageIdentifier, packageInfo.Version, result.ExitCode, result?.InstalledApps?.Count()); + logger?.LogInformation("Installed {InstallerFilename} in sandbox, reported exitcode {ExitCode}, number of apps installed {AppsInstalled}", InstallerFilename, result.ExitCode, result?.InstalledApps?.Count()); logger?.LogInformation("Sandbox result: {Result}", result); - //if(Open) - //{ - - // var result = await processManager.RunProcessAsync("WindowsSandbox.exe", sandboxFile, cancellationToken); - // logger?.LogInformation("Sandbox exited, wait a bit and cleanup"); - // await Task.Delay(3000); - // Directory.Delete(outputFolder, true); - //} else - //{ - // logger?.LogInformation("Sandbox file created at {sandboxFile}", sandboxFile); - // WriteObject(sandboxFile); - //} } } diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtSetupFile.cs b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtSetupFile.cs new file mode 100644 index 0000000..ed71d85 --- /dev/null +++ b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtSetupFile.cs @@ -0,0 +1,88 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Graph.Beta; +using Svrooij.PowerShell.DependencyInjection; +using System; +using System.IO; +using System.Linq; +using System.Management.Automation; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using WingetIntune; +using WingetIntune.Graph; +using WingetIntune.Intune; +using WingetIntune.Models; +using WingetIntune.Testing; +using GraphModels = Microsoft.Graph.Beta.Models; + +namespace Svrooij.WinTuner.CmdLets.Commands; +/// +/// Test your silent install switches +/// Test if a setup will install on the Windows Sandbox +/// Documentation +/// +/// +/// Test any installer in sandbox +/// Test-WtSetupFile -SetupFile D:\packages\xyz.exe -Installer "all your arguments" +/// +[Cmdlet(VerbsDiagnostic.Test, "WtSetupFile")] +[OutputType(typeof(string))] +public class TestWtSetupFile : DependencyCmdlet +{ + /// + /// The absolute path to your setup file + /// + [Parameter( + Mandatory = true, + Position = 0, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = true, + HelpMessage = "Absolute path to your setup file")] + public string? SetupFile { get; set; } + + /// + /// Override the installer arguments + /// + [Parameter( + Mandatory = false, + Position = 1, + ValueFromPipeline = false, + ValueFromPipelineByPropertyName = false, + HelpMessage = "Override the installer arguments")] + public string? InstallerArguments { get; set; } + + /// + /// Sleep for x seconds before closing + /// + [Parameter( + Mandatory = false, + HelpMessage = "Sleep for x seconds before auto shutdown")] + public int? Sleep { get; set; } + + [ServiceDependency] + private ILogger? logger; + + [ServiceDependency] + private WindowsSandbox? sandbox; + + [ServiceDependency] + private MetadataManager? metadataManager; + + /// + public override async Task ProcessRecordAsync(CancellationToken cancellationToken) + { + + + var sandboxFile = await sandbox!.PrepareSandboxForInstaller(SetupFile!, InstallerArguments, Sleep, cancellationToken); + logger?.LogDebug("Sandbox file created at {sandboxFile}", sandboxFile); + var result = await sandbox.RunSandbox(sandboxFile, true, cancellationToken); + if (result is null) + { + logger?.LogError("Sandbox exited with null result"); + return; + } + + logger?.LogInformation("Installed {InstallerFilename} in sandbox, reported exitcode {ExitCode}, number of apps installed {AppsInstalled}", Path.GetFileName(SetupFile), result.ExitCode, result?.InstalledApps?.Count()); + logger?.LogInformation("Sandbox result: {Result}", result); + } +} diff --git a/src/WingetIntune/Intune/MetadataManager.cs b/src/WingetIntune/Intune/MetadataManager.cs index c4ba390..10c95b6 100644 --- a/src/WingetIntune/Intune/MetadataManager.cs +++ b/src/WingetIntune/Intune/MetadataManager.cs @@ -10,18 +10,32 @@ using WingetIntune.Models; namespace WingetIntune.Intune; +/// +/// Makes it easier to work with wintuner metadata files. +/// public class MetadataManager { private readonly ILogger logger; private readonly IFileManager fileManager; private readonly Mapper mapper = new(); + /// + /// + /// + /// + /// public MetadataManager(ILogger logger, IFileManager fileManager) { this.logger = logger; this.fileManager = fileManager; } + /// + /// Loads the package info from a folder. + /// + /// Folder where WinTuner placed a file + /// + /// public async Task LoadPackageInfoFromFolderAsync(string folder, CancellationToken cancellationToken) { logger.LogDebug("Loading package info from {folder}", folder); @@ -49,9 +63,22 @@ public async Task LoadPackageInfoFromFolderAsync(string folder, Can return result; } + /// + /// Loads the package info from a folder, with packageId and version + /// + /// The Root package filer + /// Package ID of previously packaged app + /// Version of the app + /// + /// Combines // to a path to get the metadata from public Task LoadPackageInfoFromFolderAsync(string rootFolder, string packageId, string version, CancellationToken cancellationToken) => LoadPackageInfoFromFolderAsync(Path.Combine(rootFolder, packageId, version), cancellationToken); + /// + /// Converts a package info to a Win32App to upload to Graph + /// + /// + /// public Win32LobApp ConvertPackageInfoToWin32App(PackageInfo packageInfo) { logger.LogDebug("Converting package info to Win32App"); @@ -59,6 +86,12 @@ public Win32LobApp ConvertPackageInfoToWin32App(PackageInfo packageInfo) return win32App; } + /// + /// Gets the IntuneWin file name from a package folder + /// + /// + /// + /// public string GetIntuneWinFileName(string packageFolder, PackageInfo packageInfo) { return Path.Combine(packageFolder, Path.GetFileNameWithoutExtension(packageInfo.InstallerFilename!) + ".intunewin"); diff --git a/src/WingetIntune/Testing/WindowsSandbox.cs b/src/WingetIntune/Testing/WindowsSandbox.cs index db9e195..2b56d2a 100644 --- a/src/WingetIntune/Testing/WindowsSandbox.cs +++ b/src/WingetIntune/Testing/WindowsSandbox.cs @@ -8,12 +8,21 @@ using WingetIntune.Models; namespace WingetIntune.Testing; + +/// +/// Helper to test packages in the Windows Sandbox +/// public class WindowsSandbox { private readonly ILogger logger; private readonly Packager packager; private readonly IProcessManager processManager; + /// + /// + /// + /// + /// public WindowsSandbox(ILoggerFactory loggerFactory, IProcessManager processManager) { this.logger = loggerFactory.CreateLogger(); @@ -21,25 +30,85 @@ public WindowsSandbox(ILoggerFactory loggerFactory, IProcessManager processManag this.processManager = processManager; } - public async Task PrepareSandboxFileForPackage(PackageInfo packageInfo, string intuneWinFile, string outputFolder, int? timeout = null, CancellationToken cancellationToken = default) + /// + /// Prepares a sandbox file for a package + /// + /// The absolute path to the .intunewin file + /// Name of the setup file inside the intune win, if not provided the name from the intunewin metadata will be used + /// Silent arguments for this setup + /// When a number above -1 is provided a shutdown command will be added to the script + /// + /// The location of the sandbox file, which may be started with method. + /// Will decrypt the intunewin file to a temp folder, create install scripts and creates a Windows Sandbox file. + public async Task PrepareSandboxFileForPackage(string intuneWinFile, string? installerFilename, string? installerArguments, int? timeout = null, CancellationToken cancellationToken = default) { - logger.LogInformation("Preparing sandbox file for {PackageId} {Version}", packageInfo.PackageIdentifier, packageInfo.Version); + var outputFolder = Path.Combine(Path.GetTempPath(), "wintuner-sandbox", Guid.NewGuid().ToString()); + logger.LogInformation("Preparing sandbox file for {IntuneWinFile}", intuneWinFile); + //logger.LogInformation("Preparing sandbox file for {PackageId} {Version}", packageInfo.PackageIdentifier, packageInfo.Version); var installerFolder = Path.Combine(outputFolder, "installer"); var logsFolder = Path.Combine(outputFolder, "logs"); Directory.CreateDirectory(installerFolder); Directory.CreateDirectory(logsFolder); - await packager.Unpack(intuneWinFile, installerFolder, cancellationToken); + var info = await packager.Unpack(intuneWinFile, installerFolder, cancellationToken); + logger.LogDebug("Unpackaged intunewin file {intuneWinFile} to {installerFolder} contained installer {InstallerFilename}", intuneWinFile, installerFolder, info?.SetupFile); + installerFilename ??= info!.SetupFile!; + if (!File.Exists(Path.Combine(installerFolder, installerFilename))) + { + throw new FileNotFoundException("Installer in the unpacked folder", installerFilename!); + } var sandboxFile = Path.Combine(outputFolder, "sandbox.wsb"); await WriteSandboxConfig(sandboxFile, installerFolder, logsFolder); - await WriteTestScript(installerFolder, packageInfo, timeout); + var scriptFolder = Path.Combine(installerFolder, "wt_scripts"); + await WriteTestScript(scriptFolder, installerFilename, installerArguments, timeout); + + return sandboxFile; + } + + /// + /// Prepares a sandbox file for an installer + /// + /// Absolute path to the installer + /// Arguments that should be used + /// Want to auto shutdown the Sandbox? + /// + /// + /// + public async Task PrepareSandboxForInstaller(string setupFile, string? installerArguments, int? timeout = null, CancellationToken cancellationToken = default) + { + var outputFolder = Path.Combine(Path.GetTempPath(), "wintuner-sandbox", Guid.NewGuid().ToString()); + var installerFolder = Directory.GetParent(setupFile)!.FullName; + logger.LogInformation("Preparing sandbox file for {setupFile}", setupFile); + + var scriptFolder = Path.Combine(outputFolder, "wt_scripts"); + var logsFolder = Path.Combine(outputFolder, "logs"); + + Directory.CreateDirectory(scriptFolder); + Directory.CreateDirectory(logsFolder); + + if (!File.Exists(setupFile)) + { + throw new FileNotFoundException("Installer file not found", setupFile); + } + + var sandboxFile = Path.Combine(outputFolder, "sandbox.wsb"); + await WriteSandboxConfig(sandboxFile, installerFolder, logsFolder, scriptFolder); + await WriteTestScript(scriptFolder, Path.GetFileName(setupFile), installerArguments, timeout); return sandboxFile; } - private static async Task WriteSandboxConfig(string sandboxFilename, string installerFolder, string logFolder) + /// + /// Creates a Windows Sandbox configuration file + /// + /// Absolute path the the sandbox file + /// Where is the installer located, this folder will be mapped to the Sandbox as readonly + /// Where should the logs be placed? This folder will be mapped to the Sandbox as writable + /// Additional script folder if not in the installer folder + /// + private static async Task WriteSandboxConfig(string sandboxFilename, string installerFolder, string logFolder, string? scriptFolder = null) { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(""); @@ -48,7 +117,7 @@ private static async Task WriteSandboxConfig(string sandboxFilename, string inst stringBuilder.AppendLine(" "); stringBuilder.AppendLine(" "); stringBuilder.AppendLine($" {installerFolder}"); - stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\installer"); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner"); stringBuilder.AppendLine(" true"); stringBuilder.AppendLine(" "); stringBuilder.AppendLine(" "); @@ -56,11 +125,19 @@ private static async Task WriteSandboxConfig(string sandboxFilename, string inst stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Desktop\\logs"); stringBuilder.AppendLine(" false"); stringBuilder.AppendLine(" "); + if (scriptFolder is not null) + { + stringBuilder.AppendLine(" "); + stringBuilder.AppendLine($" {scriptFolder}"); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\wt_scripts"); + stringBuilder.AppendLine(" true"); + stringBuilder.AppendLine(" "); + } stringBuilder.AppendLine(" "); // Startup command stringBuilder.AppendLine(" "); - stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\wintuner\\startup.cmd"); + stringBuilder.AppendLine(" c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\wt_scripts\\startup.cmd"); stringBuilder.AppendLine(" "); // Security settings @@ -75,32 +152,50 @@ private static async Task WriteSandboxConfig(string sandboxFilename, string inst await File.WriteAllTextAsync(sandboxFilename, stringBuilder.ToString()); } - private static async Task WriteTestScript(string installerFolder, PackageInfo packageInfo, int? timeout) + /// + /// Creates the test script that will be executed in the sandbox + /// + /// Script folder location, it will create the scripts here. + /// Filename of the installer + /// Arguments of the installer, will be added to the install script + /// If a value above -1 is provided, 'shutdown /s /t {timeout}' is added to the install script + /// + private static async Task WriteTestScript(string scriptFolder, string installerFilename, string? installerArguments, int? timeout) { - var arguments = packageInfo.InstallCommandLine?.Replace($"\"{packageInfo.InstallerFilename}\" ", ""); + // Create a batch script that will run a powershell script (Execution policy stuff...) var sb = new StringBuilder(); sb.AppendLine("@echo off"); - sb.AppendLine("start /wait /low powershell.exe -ExecutionPolicy Bypass -File \"C:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\wintuner\\install.ps1\""); + sb.AppendLine("start /wait /low powershell.exe -ExecutionPolicy Bypass -File \"C:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\wt_scripts\\install.ps1\""); sb.AppendLine(); - Directory.CreateDirectory(Path.Combine(installerFolder, "wintuner")); - await File.WriteAllTextAsync(Path.Combine(installerFolder, "wintuner", "startup.cmd"), sb.ToString()); + Directory.CreateDirectory(scriptFolder); + await File.WriteAllTextAsync(Path.Combine(scriptFolder, "startup.cmd"), sb.ToString()); sb.Clear(); // Create the powershell script that will install the app // and collect the installed apps // This script will also shutdown the sandbox after the installation (if a timeout above -1 is provided) - sb.AppendLine("Start-Transcript -Path c:\\Users\\WDAGUtilityAccount\\Desktop\\logs\\wintuner.log -Append -Force"); sb.AppendLine("Write-Host \"Starting installation\""); - sb.AppendLine($"Write-Host \"Installer: {packageInfo.InstallerFilename}\""); - sb.AppendLine($"Write-Host \"Arguments: {arguments}\""); + sb.AppendLine($"Write-Host \"Installer: {installerFilename}\""); + sb.AppendLine($"Write-Host \"Arguments: {installerArguments}\""); // execute the installer and capture the exit code in powershell - sb.AppendLine($"& c:\\Users\\WDAGUtilityAccount\\Downloads\\installer\\{packageInfo.InstallerFilename} {arguments}"); + //sb.AppendLine($"& \"c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\{installerFilename}\" {installerArguments}"); //sb.AppendLine("& cmd exit /b 5"); // This is a dummy command to test the exit code - sb.AppendLine("$exitCode = $LASTEXITCODE"); + //sb.AppendLine("$exitCode = $LASTEXITCODE"); + + if (string.IsNullOrWhiteSpace(installerArguments)) + { + sb.AppendLine($"$setupProcess = Start-Process -FilePath \"c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\{installerFilename}\" -Wait -PassThru"); + } + else + { + sb.AppendLine($"$setupProcess = Start-Process -FilePath \"c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\{installerFilename}\" -ArgumentList \"{installerArguments}\" -Wait -PassThru"); + } + + sb.AppendLine("$exitCode = $setupProcess.ExitCode"); sb.AppendLine("Write-Host \"Installer finished with exitcode $exitCode\""); // write the exit code to a file @@ -127,9 +222,16 @@ private static async Task WriteTestScript(string installerFolder, PackageInfo pa } // Exit with the exit code of the installer (not sure if that does anything) sb.AppendLine("exit $exitCode"); - await File.WriteAllTextAsync(Path.Combine(installerFolder, "wintuner", "install.ps1"), sb.ToString()); + await File.WriteAllTextAsync(Path.Combine(scriptFolder, "install.ps1"), sb.ToString()); } + /// + /// Runs a sandbox file + /// + /// Absolute path of the .wsb file + /// Should we try to cleanup the folder containing the sandbox file? + /// In case you want to cancel the process. + /// public async Task RunSandbox(string sandboxFile, bool cleanup, CancellationToken cancellationToken) { logger.LogInformation("Running sandbox {sandboxFile}", sandboxFile); @@ -151,9 +253,9 @@ private static async Task WriteTestScript(string installerFolder, PackageInfo pa var exitCodeFile = Path.Combine(logDirectory, "exitcode.txt"); result = new SandboxResult { - ExitCode = File.Exists(exitCodeFile) && int.TryParse(await File.ReadAllTextAsync(exitCodeFile), out int exitCode) ? exitCode : 0, - Log = File.Exists(logFile) ? await File.ReadAllTextAsync(logFile) : null, - InstalledApps = await ParseInstalledApps(Path.Combine(logDirectory, "installed.csv")) + ExitCode = File.Exists(exitCodeFile) && int.TryParse(await File.ReadAllTextAsync(exitCodeFile, cancellationToken), out int exitCode) ? exitCode : 0, + Log = File.Exists(logFile) ? await File.ReadAllTextAsync(logFile, cancellationToken) : null, + InstalledApps = await ParseInstalledApps(Path.Combine(logDirectory, "installed.csv"), cancellationToken) }; } @@ -166,7 +268,7 @@ private static async Task WriteTestScript(string installerFolder, PackageInfo pa } - private async Task?> ParseInstalledApps(string filename) + private async Task?> ParseInstalledApps(string filename, CancellationToken cancellationToken = default) { // the file is a csv with headers Version,Vendor,Name and uses , as separator @@ -177,7 +279,7 @@ private static async Task WriteTestScript(string installerFolder, PackageInfo pa return null; } - var lines = await File.ReadAllLinesAsync(filename); + var lines = await File.ReadAllLinesAsync(filename, cancellationToken); if (lines.Length < 2) { logger.LogWarning("Installed apps file is empty {filename}", filename); From 9f88d63e20c49fcdf8fcb776fdb11f2cfb035616 Mon Sep 17 00:00:00 2001 From: Stephan van Rooij <1292510+svrooij@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:37:03 +0200 Subject: [PATCH 3/4] Fixing formatting --- src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs | 4 ++-- src/WingetIntune/Testing/WindowsSandbox.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs index fd17bcd..0f43329 100644 --- a/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs +++ b/src/Svrooij.WinTuner.CmdLets/Commands/TestWtIntuneWin.cs @@ -140,7 +140,7 @@ public class TestWtIntuneWin : DependencyCmdlet public int? Sleep { get; set; } [ServiceDependency] - private ILogger? logger; + private ILogger? logger; [ServiceDependency] private WindowsSandbox? sandbox; @@ -167,7 +167,7 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke InstallerArguments ??= packageInfo.InstallCommandLine?.Replace($"\"{packageInfo.InstallerFilename!}\" ", ""); IntuneWinFile = metadataManager.GetIntuneWinFileName(PackageFolder, packageInfo); } - + if (IntuneWinFile is null) { var ex = new ArgumentException("PackageFolder was provided"); diff --git a/src/WingetIntune/Testing/WindowsSandbox.cs b/src/WingetIntune/Testing/WindowsSandbox.cs index 2b56d2a..943dc33 100644 --- a/src/WingetIntune/Testing/WindowsSandbox.cs +++ b/src/WingetIntune/Testing/WindowsSandbox.cs @@ -194,7 +194,7 @@ private static async Task WriteTestScript(string scriptFolder, string installerF { sb.AppendLine($"$setupProcess = Start-Process -FilePath \"c:\\Users\\WDAGUtilityAccount\\Downloads\\Wintuner\\{installerFilename}\" -ArgumentList \"{installerArguments}\" -Wait -PassThru"); } - + sb.AppendLine("$exitCode = $setupProcess.ExitCode"); sb.AppendLine("Write-Host \"Installer finished with exitcode $exitCode\""); From 101903dd0bcbc767c919f85b3ab760129444c96f Mon Sep 17 00:00:00 2001 From: Stephan van Rooij <1292510+svrooij@users.noreply.github.com> Date: Tue, 30 Jul 2024 18:44:29 +0200 Subject: [PATCH 4/4] Regenerating the docs --- .../Svrooij.WinTuner.CmdLets.dll-Help.xml | 567 ++++++++++++++++++ .../docs/New-WtWingetPackage.md | 17 +- .../docs/Svrooij.WinTuner.CmdLets.md | 6 + .../docs/Test-WtIntuneWin.md | 222 +++++++ .../docs/Test-WtSetupFile.md | 107 ++++ 5 files changed, 918 insertions(+), 1 deletion(-) create mode 100644 src/Svrooij.WinTuner.CmdLets/docs/Test-WtIntuneWin.md create mode 100644 src/Svrooij.WinTuner.CmdLets/docs/Test-WtSetupFile.md 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 a3e2e15..04d3033 100644 --- a/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml +++ b/src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml @@ -1928,6 +1928,18 @@ None + + Locale + + The desired locale, if available (eg. 'en-US') + + String + + String + + + None + @@ -2027,6 +2039,18 @@ None + + Locale + + The desired locale, if available (eg. 'en-US') + + String + + String + + + None + @@ -2501,6 +2525,549 @@ + + + Test-WtIntuneWin + Test + WtIntuneWin + + Test if a package will install + + + + Test if a package will install on the Windows Sandbox + + + + Test-WtIntuneWin + + PackageFolder + + The folder where the package is + + String + + String + + + None + + + InstallerArguments + + The installer arguments (if you want it to execute silently) + + String + + String + + + None + + + Clean + + Clean the test files after run + + + SwitchParameter + + + False + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + Test-WtIntuneWin + + IntuneWinFile + + The IntuneWin file to test + + String + + String + + + None + + + InstallerFilename + + The installer filename (if not set correctly inside the intunewin) + + String + + String + + + None + + + InstallerArguments + + The installer arguments (if you want it to execute silently) + + String + + String + + + None + + + Clean + + Clean the test files after run + + + SwitchParameter + + + False + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + Test-WtIntuneWin + + PackageId + + The package id to upload to Intune. + + String + + String + + + None + + + Version + + The version to upload to Intune + + String + + String + + + None + + + RootPackageFolder + + The Root folder where all the package live in. + + String + + String + + + None + + + Clean + + Clean the test files after run + + + SwitchParameter + + + False + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + + + Clean + + Clean the test files after run + + SwitchParameter + + SwitchParameter + + + False + + + InstallerArguments + + The installer arguments (if you want it to execute silently) + + String + + String + + + None + + + InstallerFilename + + The installer filename (if not set correctly inside the intunewin) + + String + + String + + + None + + + IntuneWinFile + + The IntuneWin file to test + + String + + String + + + None + + + PackageFolder + + The folder where the package is + + String + + String + + + None + + + PackageId + + The package id to upload to Intune. + + String + + String + + + None + + + RootPackageFolder + + The Root folder where all the package live in. + + String + + String + + + None + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + Version + + The version to upload to Intune + + String + + String + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + + + System.String + + + + + + + + + + System.String + + + + + + + + + + + + + + -------------------------- Example 1 -------------------------- + PS C:\> Test-WtIntuneWin -PackageFolder D:\packages\JanDeDobbeleer.OhMyPosh\22.0.3 + + Test a packaged installer in sandbox + + + + + + Online Version: + https://wintuner.app/docs/wintuner-powershell/Test-WtIntuneWin + + + + + + Test-WtSetupFile + Test + WtSetupFile + + Test your silent install switches + + + + Test if a setup will install on the Windows Sandbox + + + + Test-WtSetupFile + + SetupFile + + Absolute path to your setup file + + String + + String + + + None + + + InstallerArguments + + Override the installer arguments + + String + + String + + + None + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + + + InstallerArguments + + Override the installer arguments + + String + + String + + + None + + + SetupFile + + Absolute path to your setup file + + String + + String + + + None + + + Sleep + + Sleep for x seconds before auto shutdown + + Int32 + + Int32 + + + None + + + ProgressAction + + {{ Fill ProgressAction Description }} + + ActionPreference + + ActionPreference + + + None + + + + + + System.String + + + + + + + + + + System.String + + + + + + + + + + + + + + -------------------------- Example 1 -------------------------- + PS C:\> Test-WtSetupFile -SetupFile D:\packages\xyz.exe -Installer "all your arguments" + + Test any installer in sandbox + + + + + + Online Version: + https://wintuner.app/docs/wintuner-powershell/Test-WtSetupFile + + + Unprotect-IntuneWinPackage diff --git a/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md b/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md index 60ca28f..2824d26 100644 --- a/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md +++ b/src/Svrooij.WinTuner.CmdLets/docs/New-WtWingetPackage.md @@ -15,7 +15,7 @@ Create intunewin file from Winget installer ``` New-WtWingetPackage [-PackageId] [[-PackageFolder] ] [[-Version] ] [[-TempFolder] ] [-Architecture ] [-InstallerContext ] - [-PackageScript ] [-ProgressAction ] [] + [-PackageScript ] [-Locale ] [-ProgressAction ] [] ``` ## DESCRIPTION @@ -152,6 +152,21 @@ Accept pipeline input: True (ByPropertyName, ByValue) Accept wildcard characters: False ``` +### -Locale +The desired locale, if available (eg. 'en-US') + +```yaml +Type: String +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/Svrooij.WinTuner.CmdLets/docs/Svrooij.WinTuner.CmdLets.md b/src/Svrooij.WinTuner.CmdLets/docs/Svrooij.WinTuner.CmdLets.md index a3992a3..4ab6b7e 100644 --- a/src/Svrooij.WinTuner.CmdLets/docs/Svrooij.WinTuner.CmdLets.md +++ b/src/Svrooij.WinTuner.CmdLets/docs/Svrooij.WinTuner.CmdLets.md @@ -32,6 +32,12 @@ Remove an app from Intune ### [Search-WtWinGetPackage](Search-WtWinGetPackage.md) Search for packages in winget +### [Test-WtIntuneWin](Test-WtIntuneWin.md) +Test if a package will install + +### [Test-WtSetupFile](Test-WtSetupFile.md) +Test your silent install switches + ### [Unprotect-IntuneWinPackage](Unprotect-IntuneWinPackage.md) Decrypt an IntuneWin package diff --git a/src/Svrooij.WinTuner.CmdLets/docs/Test-WtIntuneWin.md b/src/Svrooij.WinTuner.CmdLets/docs/Test-WtIntuneWin.md new file mode 100644 index 0000000..07d9a71 --- /dev/null +++ b/src/Svrooij.WinTuner.CmdLets/docs/Test-WtIntuneWin.md @@ -0,0 +1,222 @@ +--- +external help file: Svrooij.WinTuner.CmdLets.dll-Help.xml +Module Name: Svrooij.WinTuner.CmdLets +online version: https://wintuner.app/docs/wintuner-powershell/Test-WtIntuneWin +schema: 2.0.0 +--- + +# Test-WtIntuneWin + +## SYNOPSIS +Test if a package will install + +## SYNTAX + +### PackageFolder (Default) +``` +Test-WtIntuneWin [-PackageFolder] [[-InstallerArguments] ] [-Clean] [-Sleep ] + [-ProgressAction ] [] +``` + +### WinGet +``` +Test-WtIntuneWin [-PackageId] [-Version] [-RootPackageFolder] [-Clean] + [-Sleep ] [-ProgressAction ] [] +``` + +### IntuneWin +``` +Test-WtIntuneWin [-IntuneWinFile] [[-InstallerFilename] ] [[-InstallerArguments] ] + [-Clean] [-Sleep ] [-ProgressAction ] [] +``` + +## DESCRIPTION +Test if a package will install on the Windows Sandbox + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Test-WtIntuneWin -PackageFolder D:\packages\JanDeDobbeleer.OhMyPosh\22.0.3 +``` + +Test a packaged installer in sandbox + +## PARAMETERS + +### -Clean +Clean the test files after run + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InstallerArguments +The installer arguments (if you want it to execute silently) + +```yaml +Type: String +Parameter Sets: PackageFolder +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +```yaml +Type: String +Parameter Sets: IntuneWin +Aliases: + +Required: False +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InstallerFilename +The installer filename (if not set correctly inside the intunewin) + +```yaml +Type: String +Parameter Sets: IntuneWin +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -IntuneWinFile +The IntuneWin file to test + +```yaml +Type: String +Parameter Sets: IntuneWin +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PackageFolder +The folder where the package is + +```yaml +Type: String +Parameter Sets: PackageFolder +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PackageId +The package id to upload to Intune. + +```yaml +Type: String +Parameter Sets: WinGet +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -RootPackageFolder +The Root folder where all the package live in. + +```yaml +Type: String +Parameter Sets: WinGet +Aliases: + +Required: True +Position: 2 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Sleep +Sleep for x seconds before auto shutdown + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Version +The version to upload to Intune + +```yaml +Type: String +Parameter Sets: WinGet +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +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). + +## INPUTS + +### System.String + +## OUTPUTS + +### System.String + +## NOTES + +## RELATED LINKS diff --git a/src/Svrooij.WinTuner.CmdLets/docs/Test-WtSetupFile.md b/src/Svrooij.WinTuner.CmdLets/docs/Test-WtSetupFile.md new file mode 100644 index 0000000..acdf3c8 --- /dev/null +++ b/src/Svrooij.WinTuner.CmdLets/docs/Test-WtSetupFile.md @@ -0,0 +1,107 @@ +--- +external help file: Svrooij.WinTuner.CmdLets.dll-Help.xml +Module Name: Svrooij.WinTuner.CmdLets +online version: https://wintuner.app/docs/wintuner-powershell/Test-WtSetupFile +schema: 2.0.0 +--- + +# Test-WtSetupFile + +## SYNOPSIS +Test your silent install switches + +## SYNTAX + +``` +Test-WtSetupFile [-SetupFile] [[-InstallerArguments] ] [-Sleep ] + [-ProgressAction ] [] +``` + +## DESCRIPTION +Test if a setup will install on the Windows Sandbox + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> Test-WtSetupFile -SetupFile D:\packages\xyz.exe -Installer "all your arguments" +``` + +Test any installer in sandbox + +## PARAMETERS + +### -InstallerArguments +Override the installer arguments + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SetupFile +Absolute path to your setup file + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Sleep +Sleep for x seconds before auto shutdown + +```yaml +Type: Int32 +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +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). + +## INPUTS + +### System.String + +## OUTPUTS + +### System.String + +## NOTES + +## RELATED LINKS