Skip to content

Commit

Permalink
Getting first release of powershell module ready (#39)
Browse files Browse the repository at this point in the history
* PowerShell module release
* fix: Async download installers Fixed #38
  • Loading branch information
svrooij authored Apr 3, 2024
1 parent 763a5a9 commit 6ccc6da
Show file tree
Hide file tree
Showing 28 changed files with 338 additions and 99 deletions.
28 changes: 26 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,30 @@ jobs:
- name: 🎒 Load packages
run: dotnet restore

- name: 🛠️ Build code
- name: 📝 Set module version
shell: pwsh
id: version
run: |
$version = "${{ github.ref_name }}".Substring(1)
$module = Get-Content -Path src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.psd1
$module = $module -replace 'ModuleVersion = ''\d+\.\d+\.\d+''', "ModuleVersion = '$version'"
$module | Set-Content -Path src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.psd1
- name: 🛠️ Build module
shell: pwsh
run: dotnet build ./src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.csproj --configuration Release --no-restore -p:Version=$("${{ github.ref_name }}".Substring(1)) -o ./dist/WinTuner

- name: 🧪 Run tests (import module)
shell: pwsh
run: dotnet build ./src/Svrooij.WinTuner.CmdLets/Svrooij.WinTuner.CmdLets.csproj --configuration Release --no-restore -p:Version=$("${{ github.ref_name }}".Substring(1))
run: |
Import-Module ./dist/WinTuner/WinTuner.psd1
Get-Command -Module WinTuner
Get-Command -Module WinTuner | Select-Object -ExpandProperty Name | ForEach-Object { Get-Help -Name $_ -Full }
- name: 📦 Publish WinTuner to PowerShell Gallery
shell: pwsh
run: |
Import-Module ./dist/WinTuner/WinTuner.psd1
Publish-Module -Path ./dist/WinTuner -NuGetApiKey $env:PSGALLERY_TOKEN -Repository PSGallery -Force
env:
PSGALLERY_TOKEN: ${{ secrets.PSGALLERY_TOKEN }}
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,7 @@ MigrationBackup/
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd
FodyWeavers.xsd

# Build artifacts
dist
72 changes: 56 additions & 16 deletions src/Svrooij.WinTuner.CmdLets/Commands/BaseIntuneCmdlet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ namespace Svrooij.WinTuner.CmdLets.Commands;
public abstract class BaseIntuneCmdlet : DependencyCmdlet<Startup>
{

/// <summary>
///
/// </summary>
[Parameter(
Mandatory = false,
Position = 20,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Use a managed identity to connect to Intune")]
public bool UseManagedIdentity { get; set; }

/// <summary>
///
/// </summary>
Expand All @@ -27,19 +38,19 @@ public abstract class BaseIntuneCmdlet : DependencyCmdlet<Startup>
Position = 21,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Use a token from another source to connect to Intune")]
public string? Token { get; set; }
HelpMessage = "Use default Azure Credentials from Azure.Identity to connect to Intune")]
public bool UseDefaultAzureCredential { get; set; }

/// <summary>
///
/// </summary>
[Parameter(
Mandatory = false,
Position = 20,
Position = 22,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Use a managed identity to connect to Intune")]
public bool UseManagedIdentity { get; set; }
HelpMessage = "Use a token from another source to connect to Intune")]
public string? Token { get; set; }

/// <summary>
///
Expand All @@ -60,8 +71,8 @@ public abstract class BaseIntuneCmdlet : DependencyCmdlet<Startup>
Position = 26,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the tenant ID, if you want to use another tenant then your home tenant")]
public string? TenantId { get; set; }
HelpMessage = "Specify the tenant ID, optional for interactive, mandatory for Client Credentials flow. Loaded from `AZURE_TENANT_ID`")]
public string? TenantId { get; set; } = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");

/// <summary>
///
Expand All @@ -71,10 +82,25 @@ public abstract class BaseIntuneCmdlet : DependencyCmdlet<Startup>
Position = 27,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "(optionally) Use a different client ID, apart from the default configured one.")]
public string? ClientId { get; set; }
HelpMessage = "Specify the client ID, optional for interactive, mandatory for Client Credentials flow. Loaded from `AZURE_CLIENT_ID`")]
public string? ClientId { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID");


internal string[] DefaultScopes { get; set; } = new[] { "DeviceManagementConfiguration.ReadWrite.All", "DeviceManagementApps.ReadWrite.All" };
/// <summary>
///
/// </summary>
[Parameter(
Mandatory = false,
Position = 28,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Specify the client secret, mandatory for Client Credentials flow. Loaded from `AZURE_CLIENT_SECRET`")]
public string? ClientSecret { get; set; } = Environment.GetEnvironmentVariable("AZURE_CLIENT_SECRET");

/// <summary>
///
/// </summary>
internal static string[] DefaultScopes { get; } = new[] { "DeviceManagementConfiguration.ReadWrite.All", "DeviceManagementApps.ReadWrite.All" };

internal void ValidateAuthenticationParameters()
{
Expand All @@ -83,7 +109,7 @@ internal void ValidateAuthenticationParameters()
return;
}

if (UseManagedIdentity)
if (UseManagedIdentity || UseDefaultAzureCredential)
{
return;
}
Expand All @@ -93,23 +119,37 @@ internal void ValidateAuthenticationParameters()
return;
}

throw new ArgumentException($"Use `{nameof(Token)}`, `{nameof(UseManagedIdentity)}` or `{nameof(Username)}` to select the graph connection type", nameof(ParameterSetName));
throw new ArgumentException($"Use `{nameof(Token)}`, `{nameof(UseManagedIdentity)}`, `{nameof(UseDefaultAzureCredential)}` or `{nameof(Username)}` to select the graph connection type", nameof(ParameterSetName));
}

internal IAuthenticationProvider CreateAuthenticationProviderAsync(string[]? scopes = null)
internal IAuthenticationProvider CreateAuthenticationProvider(string[]? scopes = null)
{
if (!string.IsNullOrEmpty(Token))
{
return new WingetIntune.Internal.Msal.StaticAuthenticationProvider(Token);
}

if (UseManagedIdentity)
if (UseManagedIdentity || UseDefaultAzureCredential)
{
// Maybe make which credentials to use configurable
var credentials = new Azure.Identity.DefaultAzureCredential();
Azure.Core.TokenCredential credentials = UseManagedIdentity
? new Azure.Identity.ManagedIdentityCredential(ClientId)
: new Azure.Identity.DefaultAzureCredential();
return new Microsoft.Graph.Authentication.AzureIdentityAuthenticationProvider(credentials, null, null, scopes ?? DefaultScopes);
}

if (!string.IsNullOrEmpty(ClientId) && !string.IsNullOrEmpty(ClientSecret) && !string.IsNullOrEmpty(TenantId))
{
return new Microsoft.Graph.Authentication.AzureIdentityAuthenticationProvider(new Azure.Identity.ClientSecretCredential(TenantId, ClientId, ClientSecret, new Azure.Identity.ClientSecretCredentialOptions
{
TokenCachePersistenceOptions = new Azure.Identity.TokenCachePersistenceOptions
{
Name = "WinTuner-PowerShell",
UnsafeAllowUnencryptedStorage = true,
}
}), scopes: scopes ?? DefaultScopes);
}

if (!string.IsNullOrEmpty(Username))
{
return new WingetIntune.Internal.Msal.InteractiveAuthenticationProvider(new WingetIntune.Internal.Msal.InteractiveAuthenticationProviderOptions
Expand All @@ -126,7 +166,7 @@ internal IAuthenticationProvider CreateAuthenticationProviderAsync(string[]? sco

internal GraphServiceClient CreateGraphServiceClient(HttpClient httpClient, string[]? scopes = null)
{
var authenticationProvider = CreateAuthenticationProviderAsync(scopes ?? DefaultScopes);
var authenticationProvider = CreateAuthenticationProvider(scopes ?? DefaultScopes);
var graphServiceClient = new GraphServiceClient(httpClient: httpClient, authenticationProvider: authenticationProvider, baseUrl: "https://graph.microsoft.com/beta");

return graphServiceClient;
Expand Down
18 changes: 10 additions & 8 deletions src/Svrooij.WinTuner.CmdLets/Commands/DeployWtWin32App.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ namespace Svrooij.WinTuner.CmdLets.Commands;
/// <para type="description">Upload a pre-packaged application, from just it's folder, using interactive authentication</para>
/// <code>Deploy-WtWin32App -PackageFolder C:\Tools\packages\JanDeDobbeleer.OhMyPosh\19.5.2 -Username [email protected]</code>
/// </example>
[Cmdlet(VerbsLifecycle.Deploy, "WtWin32App", DefaultParameterSetName = nameof(App))]
[Cmdlet(VerbsLifecycle.Deploy, "WtWin32App", DefaultParameterSetName = ParameterSetApp)]
[OutputType(typeof(GraphModels.Win32LobApp))]
public class DeployWtWin32App : BaseIntuneCmdlet
{
private const string ParameterSetApp = "Win32LobApp";
private const string ParameterSetWinGet = "WinGet";
/// <summary>
/// <para type="description">The Win32LobApp configuration you want to create</para>
/// </summary>
[Parameter(
Mandatory = true,
Position = 0,
ParameterSetName = nameof(App),
ParameterSetName = ParameterSetApp,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = false,
HelpMessage = "The App configuration you want to create")]
Expand All @@ -41,7 +43,7 @@ public class DeployWtWin32App : BaseIntuneCmdlet
[Parameter(
Mandatory = true,
Position = 1,
ParameterSetName = nameof(App),
ParameterSetName = ParameterSetApp,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = false,
HelpMessage = "The .intunewin file that should be added to this app")]
Expand All @@ -53,7 +55,7 @@ public class DeployWtWin32App : BaseIntuneCmdlet
[Parameter(
Mandatory = false,
Position = 2,
ParameterSetName = nameof(App),
ParameterSetName = ParameterSetApp,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = false,
HelpMessage = "Load the logo from file")]
Expand All @@ -65,7 +67,7 @@ public class DeployWtWin32App : BaseIntuneCmdlet
[Parameter(
Mandatory = true,
Position = 0,
ParameterSetName = nameof(PackageId),
ParameterSetName = ParameterSetWinGet,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = false,
HelpMessage = "The package id to upload to Intune.")]
Expand All @@ -77,7 +79,7 @@ public class DeployWtWin32App : BaseIntuneCmdlet
[Parameter(
Mandatory = true,
Position = 1,
ParameterSetName = nameof(PackageId),
ParameterSetName = ParameterSetWinGet,
ValueFromPipeline = false,
HelpMessage = "The version to upload to Intune"
)]
Expand All @@ -89,7 +91,7 @@ public class DeployWtWin32App : BaseIntuneCmdlet
[Parameter(
Mandatory = true,
Position = 2,
ParameterSetName = nameof(PackageId),
ParameterSetName = ParameterSetWinGet,
ValueFromPipeline = false,
HelpMessage = "The Root folder where all the package live in.")]
public string? RootPackageFolder { get; set; }
Expand Down Expand Up @@ -125,7 +127,7 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke

if (App is null)
{
if (ParameterSetName == nameof(PackageId))
if (ParameterSetName == ParameterSetWinGet)
{
PackageFolder = Path.Combine(RootPackageFolder!, PackageId!, Version!);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Svrooij.WinTuner.CmdLets/Commands/NewIntuneWinPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class NewIntuneWinPackage : DependencyCmdlet<Startup>
Position = 2,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
HelpMessage = "Destination folder")]
HelpMessage = "Destination folder, should be outside the source folder")]
public string? DestinationPath { get; set; }


Expand All @@ -67,7 +67,7 @@ public override async Task ProcessRecordAsync(CancellationToken cancellationToke
var setupFile = Path.Combine(SourcePath!, SetupFile!);
if (!Directory.Exists(DestinationPath!))
{
WriteVerbose($"Creating destination folder {DestinationPath}");
_logger?.LogInformation("Creating destination folder {DestinationPath}", DestinationPath);
Directory.CreateDirectory(DestinationPath!);
}
_logger?.LogInformation("Creating package for {setupFile}", setupFile);
Expand Down
17 changes: 11 additions & 6 deletions src/Svrooij.WinTuner.CmdLets/Commands/NewWtWingetPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Svrooij.WinTuner.CmdLets.Commands;
/// </summary>
/// <example>
/// <para type="description">Package all files in C:\Temp\Source, with setup file ..\setup.exe to the specified folder</para>
/// <code>New-WingetPackage -PackageId JanDeDobbeleer.OhMyPosh -PackageFolder C:\Tools\Packages</code>
/// <code>New-WtWingetPackage -PackageId JanDeDobbeleer.OhMyPosh -PackageFolder C:\Tools\Packages</code>
/// </example>
[Cmdlet(VerbsCommon.New, "WtWingetPackage")]
[OutputType(typeof(WingetIntune.Models.WingetPackage))]
Expand All @@ -32,7 +32,7 @@ public class NewWtWingetPackage : DependencyCmdlet<Startup>
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
HelpMessage = "The package id to download")]
public string PackageId { get; set; }
public string? PackageId { get; set; }

/// <summary>
/// The folder to store the package in
Expand Down Expand Up @@ -83,22 +83,27 @@ public class NewWtWingetPackage : DependencyCmdlet<Startup>
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
{
// Fix the package id casing.
PackageId = await wingetRepository.GetPackageId(PackageId, cancellationToken);
PackageId = (await wingetRepository!.GetPackageId(PackageId!, cancellationToken)) ?? string.Empty;
if (string.IsNullOrEmpty(PackageId))
{
logger.LogWarning("Package {PackageId} not found", PackageId);
return;
}
if (string.IsNullOrEmpty(Version))
{
Version = await wingetRepository.GetLatestVersion(PackageId, cancellationToken);
Version = await wingetRepository.GetLatestVersion(PackageId!, cancellationToken);
}

logger.LogInformation("Packaging package {PackageId} {Version}", PackageId, Version);

var packageInfo = await repository.GetPackageInfoAsync(PackageId, Version, source: "winget", cancellationToken: cancellationToken);
var packageInfo = await repository.GetPackageInfoAsync(PackageId!, Version, source: "winget", cancellationToken: cancellationToken);

if (packageInfo != null)
{
logger.LogDebug("Package {PackageId} {Version} from {Source}", packageInfo.PackageIdentifier, packageInfo.Version, packageInfo.Source);

var package = await intuneManager.GenerateInstallerPackage(
TempFolder,
TempFolder!,
PackageFolder,
packageInfo,
cancellationToken: cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class UnprotectIntuneWinPackage : DependencyCmdlet<Startup>
[ServiceDependency]
private ILogger<UnprotectIntuneWinPackage> logger;

/// <inheritdoc/>
public override async Task ProcessRecordAsync(CancellationToken cancellationToken)
{
try
Expand Down
8 changes: 7 additions & 1 deletion src/Svrooij.WinTuner.CmdLets/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# WinTuner CmdLets
# WinTuner PowerShell module

## Refresh documentation

```PowerShell
New-MarkdownHelp -Module "Svrooij.WinTuner.CmdLets" -OutputFolder "..\..\..\docs" -WithModulePage -Force
```

## Create a package and deploy to Intune

```PowerShell
New-WtWingetPackage -PackageId Jandedobbeleer.ohmyposh -PackageFolder C:\tools\packages\ | Deploy-WtWin32App -Username [email protected]
```
3 changes: 1 addition & 2 deletions src/Svrooij.WinTuner.CmdLets/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
using Svrooij.PowerShell.DependencyInjection;
using Svrooij.PowerShell.DependencyInjection.Logging;
using System;
using System.Collections.Generic;
using System.Text;
using WingetIntune;

namespace Svrooij.WinTuner.CmdLets;

/// <inheritdoc/>
public class Startup : PsStartup
{
/// <inheritdoc/>
Expand Down
Loading

0 comments on commit 6ccc6da

Please sign in to comment.