Skip to content

Commit

Permalink
Cross-platform MSI info (#155)
Browse files Browse the repository at this point in the history
Reading details from MSI files, was done by a windows only dll.
This allows loading those details on any platform by using [OpenMCDF](https://github.com/ironfede/openmcdf).

- Packaging MSI files will now work on Linux/mac
- `Show-MsiInfo` command to allow reading Msi info for use in your own scripts.

Co-authored-by: Miyoyo <[email protected]>
  • Loading branch information
svrooij and miyoyo authored Dec 23, 2024
1 parent 15f81e0 commit 94f4796
Show file tree
Hide file tree
Showing 22 changed files with 826 additions and 701 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
- name: 📝 Code Coverage report
run: |
dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.2.5
reportgenerator -reports:${{github.workspace}}/coverage.cobertura.xml -targetdir:${{github.workspace}}/report -reporttypes:MarkdownSummaryGithub -filefilters:-*.g.cs "-classfilters:-WixSharp.*;-WingetIntune.Os.*;-WingetIntune.Internal.MsStore.Models.*" -verbosity:Warning
reportgenerator -reports:${{github.workspace}}/coverage.cobertura.xml -targetdir:${{github.workspace}}/report -reporttypes:MarkdownSummaryGithub -filefilters:-*.g.cs "-classfilters:-WingetIntune.Os.*;-WingetIntune.Internal.MsStore.Models.*" -verbosity:Warning
sed -i 's/# Summary/## 📝 Code Coverage/g' ${{github.workspace}}/report/SummaryGithub.md
sed -i 's/## Coverage/### 📝 Code Coverage details/g' ${{github.workspace}}/report/SummaryGithub.md
cat ${{github.workspace}}/report/*.md >> $GITHUB_STEP_SUMMARY
Expand Down
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMinor"
}
}
95 changes: 95 additions & 0 deletions src/Svrooij.WinTuner.CmdLets/Commands/ShowMsiInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.IO;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Svrooij.PowerShell.DependencyInjection;
using WingetIntune;
using WingetIntune.Msi;

namespace Svrooij.WinTuner.CmdLets.Commands;

/// <summary>
/// <para type="synopsis">Show information about an MSI file</para>
/// <para type="description">Show information about an MSI file, this includes the MSI code and version.</para>
/// </summary>
/// <psOrder>100</psOrder>
/// <example>
/// <para type="name">Show information about an MSI file</para>
/// <para type="description">Show information about an MSI file, this includes the MSI code and version.</para>
/// <code>Show-MsiInfo -MsiPath "C:\path\to\file.msi"</code>
/// </example>
/// <example>
/// <para type="name">Show information about an MSI file from URL</para>
/// <para type="description">Download an MSI file and show the details</para>
/// <code>Show-MsiInfo -MsiUrl "https://example.com/file.msi" -OutputPath "C:\path\to"</code>
/// </example>
[Cmdlet(VerbsCommon.Show, "MsiInfo", HelpUri = "https://wintuner.app/docs/wintuner-powershell/Show-MsiInfo", DefaultParameterSetName = nameof(MsiPath))]
[OutputType(typeof(Models.MsiInfo))]
public class ShowMsiInfo : DependencyCmdlet<Startup>
{
/// <summary>
/// <para type="description">Path to the MSI file</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = nameof(MsiPath))]
public string? MsiPath { get; set; }

/// <summary>
/// <para type="description">URL to the MSI file</para>
/// </summary>
[Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = nameof(MsiUrl))]
public Uri? MsiUrl { get; set; }

/// <summary>
/// <para type="description">Path to save the MSI file</para>
/// </summary>
[Parameter(Mandatory = false, Position = 1, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = nameof(MsiUrl))]
public string? OutputPath { get; set; }

/// <summary>
/// <para type="description">Filename to save the MSI file, if cannot be discovered from url</para>
/// </summary>
[Parameter(Mandatory = false, Position = 2, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = nameof(MsiUrl))]
public string? OutputFilename { get; set; }

[ServiceDependency]
private ILogger<ShowMsiInfo>? logger;

[ServiceDependency]
private IFileManager? fileManager;

/// <inheritdoc/>
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
{
if (MsiPath is not null)
{
logger?.LogInformation("Reading MSI from path: {MsiPath}", MsiPath);
}
else if (MsiUrl is not null)
{
OutputPath ??= Path.GetTempPath();
logger?.LogInformation("Downloading MSI from URL {MsiUrl} to {OutputPath}", MsiUrl, OutputPath);
var outputFile = Path.Combine(OutputPath, OutputFilename ?? Path.GetFileName(MsiUrl.LocalPath));
// The file managed does automatic chunking of the download, so it will also work for very large files.
await fileManager!.DownloadFileAsync(MsiUrl!.ToString(), outputFile, cancellationToken: cancellationToken);
MsiPath = outputFile;
}
else
{
throw new InvalidOperationException("Either MsiPath or MsiUrl must be set");
}

using var msiStream = new FileStream(MsiPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.Asynchronous);
var decoder = new MsiDecoder(msiStream);
var codeFromMsi = decoder.GetCode();
var versionFromMsi = decoder.GetVersion();

WriteObject(new Models.MsiInfo
{
Path = MsiPath,
ProductCode = codeFromMsi,
ProductVersion = versionFromMsi
});
}
}
21 changes: 21 additions & 0 deletions src/Svrooij.WinTuner.CmdLets/Models/MsiInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Svrooij.WinTuner.CmdLets.Models;

/// <summary>
/// Information about an MSI file
/// </summary>
public class MsiInfo
{
/// <summary>
/// The path to the MSI file
/// </summary>
public string? Path { get; set; }
/// <summary>
/// The product code of the MSI file
/// </summary>
public string? ProductCode { get; set; }

/// <summary>
/// The version of the MSI file
/// </summary>
public string? ProductVersion { get; set; }
}
173 changes: 167 additions & 6 deletions src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.dll-Help.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,17 @@ You could run this on a weekly bases.
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="10" aliases="none">
<maml:name>PartialPackage</maml:name>
<maml:description>
<maml:para>Creating a partial package means that the files are not zipped into the intunewin file, but are left as is.</maml:para>
</maml:description>
<command:parameterValue required="false" variableLength="false">SwitchParameter</command:parameterValue>
<dev:type>
<maml:name>SwitchParameter</maml:name>
</dev:type>
<dev:defaultValue>False</dev:defaultValue>
</command:parameter>
</command:syntaxItem>
</command:syntax>
<command:parameters>
Expand Down Expand Up @@ -1823,6 +1834,17 @@ You could run this on a weekly bases.
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="False" position="10" aliases="none">
<maml:name>PartialPackage</maml:name>
<maml:description>
<maml:para>Creating a partial package means that the files are not zipped into the intunewin file, but are left as is.</maml:para>
</maml:description>
<command:parameterValue required="false" variableLength="false">SwitchParameter</command:parameterValue>
<dev:type>
<maml:name>SwitchParameter</maml:name>
</dev:type>
<dev:defaultValue>False</dev:defaultValue>
</command:parameter>
</command:parameters>
<command:returnValues>
<command:returnValue>
Expand Down Expand Up @@ -1970,12 +1992,151 @@ You could run this on a weekly bases.
</dev:remarks>
</command:example>
</command:examples>
<command:relatedLinks>
<maml:navigationLink>
<maml:linkText>Online Version</maml:linkText>
<maml:uri>https://wintuner.app/docs/wintuner-powershell/Search-WtWingetPackage</maml:uri>
</maml:navigationLink>
</command:relatedLinks>
</command:command>
<command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10" xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10" xmlns:MSHelp="http://msdn.microsoft.com/mshelp" xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10">
<command:details>
<command:name>Show-MsiInfo</command:name>
<command:verb>Show</command:verb>
<command:noun>MsiInfo</command:noun>
<maml:description>
<maml:para>Show information about an MSI file</maml:para>
</maml:description>
</command:details>
<maml:description>
<maml:para>Show information about an MSI file, this includes the MSI code and version.</maml:para>
</maml:description>
<command:syntax>
<command:syntaxItem>
<maml:name>Show-MsiInfo</maml:name>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="0" aliases="none">
<maml:name>MsiPath</maml:name>
<maml:description>
<maml:para>Path to the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
</command:syntaxItem>
<command:syntaxItem>
<maml:name>Show-MsiInfo</maml:name>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="0" aliases="none">
<maml:name>MsiUrl</maml:name>
<maml:description>
<maml:para>URL to the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Uri</command:parameterValue>
<dev:type>
<maml:name>Uri</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="1" aliases="none">
<maml:name>OutputPath</maml:name>
<maml:description>
<maml:para>Path to save the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="2" aliases="none">
<maml:name>OutputFilename</maml:name>
<maml:description>
<maml:para>Filename to save the MSI file, if cannot be discovered from url</maml:para>
</maml:description>
<command:parameterValue required="false" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
</command:syntaxItem>
</command:syntax>
<command:parameters>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="0" aliases="none">
<maml:name>MsiPath</maml:name>
<maml:description>
<maml:para>Path to the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="0" aliases="none">
<maml:name>MsiUrl</maml:name>
<maml:description>
<maml:para>URL to the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">Uri</command:parameterValue>
<dev:type>
<maml:name>Uri</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="true" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="1" aliases="none">
<maml:name>OutputPath</maml:name>
<maml:description>
<maml:para>Path to save the MSI file</maml:para>
</maml:description>
<command:parameterValue required="true" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
<command:parameter required="false" variableLength="true" globbing="false" pipelineInput="True (ByPropertyName, ByValue)" position="2" aliases="none">
<maml:name>OutputFilename</maml:name>
<maml:description>
<maml:para>Filename to save the MSI file, if cannot be discovered from url</maml:para>
</maml:description>
<command:parameterValue required="false" variableLength="false">String</command:parameterValue>
<dev:type>
<maml:name>String</maml:name>
</dev:type>
<dev:defaultValue>None</dev:defaultValue>
</command:parameter>
</command:parameters>
<command:returnValues>
<command:returnValue>
<dev:type>
<maml:name>Svrooij.WinTuner.CmdLets.Models.MsiInfo</maml:name>
</dev:type>
<maml:description>
<maml:para>Svrooij.WinTuner.CmdLets.Models.MsiInfo</maml:para>
</maml:description>
</command:returnValue>
</command:returnValues>
<command:examples>
<command:example>
<maml:title>--------------------- Show information about an MSI file ---------------------</maml:title>
<dev:code>PS C:\&gt; Show-MsiInfo -MsiPath "C:\path\to\file.msi"</dev:code>
<dev:remarks>
<maml:para>Show information about an MSI file, this includes the MSI code and version.
</maml:para>
</dev:remarks>
</command:example>
<command:example>
<maml:title>---------------- Show information about an MSI file from URL -----------------</maml:title>
<dev:code>PS C:\&gt; Show-MsiInfo -MsiUrl "https://example.com/file.msi" -OutputPath "C:\path\to"</dev:code>
<dev:remarks>
<maml:para>Download an MSI file and show the details
</maml:para>
</dev:remarks>
</command:example>
</command:examples>
<command:relatedLinks>
<maml:navigationLink>
<maml:linkText>Online Version</maml:linkText>
<maml:uri>https://wintuner.app/docs/wintuner-powershell/Show-MsiInfo</maml:uri>
</maml:navigationLink>
</command:relatedLinks>
</command:command>
<command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10" xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10" xmlns:MSHelp="http://msdn.microsoft.com/mshelp" xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10">
<command:details>
Expand Down
6 changes: 6 additions & 0 deletions src/Svrooij.WinTuner.CmdLets/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,11 @@
"Newtonsoft.Json": "9.0.1"
}
},
"OpenMcdf": {
"type": "Transitive",
"resolved": "2.4.1",
"contentHash": "0XVhI+P6pe80xYntIeF6H4F7AIL4ZolMTI5vkm3QUt9XTnRJht6CNYk1eSfI8YInRble6cw91ypF5llPXzKD/A=="
},
"Riok.Mapperly": {
"type": "Transitive",
"resolved": "3.6.0",
Expand Down Expand Up @@ -1126,6 +1131,7 @@
"Microsoft.Graph.Core": "[3.2.0, )",
"Microsoft.Identity.Client.Broker": "[4.66.2, )",
"Microsoft.Identity.Client.Extensions.Msal": "[4.66.2, )",
"OpenMcdf": "[2.4.1, )",
"Riok.Mapperly": "[3.6.0, )",
"SvRooij.ContentPrep": "[0.2.2, )",
"System.IdentityModel.Tokens.Jwt": "[8.0.2, )",
Expand Down
6 changes: 6 additions & 0 deletions src/WingetIntune.Cli/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,11 @@
"Microsoft.Kiota.Abstractions": "1.14.0"
}
},
"OpenMcdf": {
"type": "Transitive",
"resolved": "2.4.1",
"contentHash": "0XVhI+P6pe80xYntIeF6H4F7AIL4ZolMTI5vkm3QUt9XTnRJht6CNYk1eSfI8YInRble6cw91ypF5llPXzKD/A=="
},
"Riok.Mapperly": {
"type": "Transitive",
"resolved": "3.6.0",
Expand Down Expand Up @@ -779,6 +784,7 @@
"Microsoft.Graph.Core": "[3.2.0, )",
"Microsoft.Identity.Client.Broker": "[4.66.2, )",
"Microsoft.Identity.Client.Extensions.Msal": "[4.66.2, )",
"OpenMcdf": "[2.4.1, )",
"Riok.Mapperly": "[3.6.0, )",
"SvRooij.ContentPrep": "[0.2.2, )",
"System.IdentityModel.Tokens.Jwt": "[8.0.2, )",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public async Task<AuthenticationResult> AccuireTokenAsync(IEnumerable<string> sc
: await publicClientApplication.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken);
return authenticationResult;
}
catch (MsalUiRequiredException ex)
catch (MsalUiRequiredException)
{
return await AcquireTokenInteractiveAsync(scopes, tenantId, account?.Username ?? userId, cancellationToken);
}
Expand Down
Loading

0 comments on commit 94f4796

Please sign in to comment.