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