Skip to content

Commit

Permalink
Invoke-Pog: WIP port to C#
Browse files Browse the repository at this point in the history
  • Loading branch information
MatejKafka committed Apr 1, 2024
1 parent f4320e9 commit 377d18b
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 74 deletions.
3 changes: 1 addition & 2 deletions app/Pog/Pog.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
AliasesToExport = @('pog')

CmdletsToExport = @(
'Invoke-Pog'
'Import-Pog'
'Install-Pog'
'Enable-Pog'
Expand All @@ -32,8 +33,6 @@
)

FunctionsToExport = @(
'Invoke-Pog'

'Update-PogManifest'
'New-PogPackage'
'New-PogImportedPackage'
Expand Down
69 changes: 2 additions & 67 deletions app/Pog/Pog.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ using module .\lib\Utils.psm1
. $PSScriptRoot\lib\header.ps1

# re-export binary cmdlets from Pog.dll
Export-ModuleMember -Cmdlet `
Import-Pog, Install-Pog, Enable-Pog, Export-Pog, Disable-Pog, Uninstall-Pog, `
Export-ModuleMember -Alias pog -Cmdlet `
Invoke-Pog, Import-Pog, Install-Pog, Enable-Pog, Export-Pog, Disable-Pog, Uninstall-Pog, `
Get-PogPackage, Get-PogRepositoryPackage, Get-PogRoot, `
Confirm-PogPackage, Confirm-PogRepositoryPackage, `
Clear-PogDownloadCache, Show-PogManifestHash
Expand All @@ -21,71 +21,6 @@ Export function Edit-PogRootList {
Start-Process $Path
}

# defined below
Export alias pog Invoke-Pog

# CmdletBinding is manually copied from Import-Pog, there doesn't seem any way to dynamically copy this like with dynamicparam
# TODO: rollback on error
# TODO: allow wildcards in PackageName and Version arguments for commands where it makes sense
Export function Invoke-Pog {
# .SYNOPSIS
# Import, install, enable and export a package.
# .DESCRIPTION
# Runs all four installation stages in order. All arguments passed to this cmdlet,
# except for the `-InstallOnly` switch, are forwarded to `Import-Pog`.
[CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = "PackageName_")]
param(
### Only import and install the package, do not enable and export.
[switch]
$InstallOnly,
### Import, install and enable the package, do not export it.
[switch]
$NoExport
)

dynamicparam {
$ParamBuilder = [Pog.Commands.Common.DynamicCommandParameters+Builder]::new("", "None", {
param($ParamName, $Attr)
throw "Cannot copy parameter '$ParamName', attribute '$($Attr.GetType().ToString())'."
})
$CopiedParams = $ParamBuilder.CopyParameters((Get-Command Import-Pog))
return $CopiedParams
}

begin {
$Params = $CopiedParams.Extract()

# reuse PassThru parameter from Import-Pog for Enable-Pog
$PassThru = [bool]$Params["PassThru"]

$LogArgs = @{}
if ($PSBoundParameters.ContainsKey("Verbose")) {$LogArgs["Verbose"] = $PSBoundParameters.Verbose}
if ($PSBoundParameters.ContainsKey("Debug")) {$LogArgs["Debug"] = $PSBoundParameters.Debug}

$null = $Params.Remove("PassThru")

$SbAll = {Import-Pog -PassThru @Params | Install-Pog -PassThru @LogArgs | Enable-Pog -PassThru @LogArgs | Export-Pog -PassThru:$PassThru @LogArgs}
$SbNoExport = {Import-Pog -PassThru @Params | Install-Pog -PassThru @LogArgs | Enable-Pog -PassThru:$PassThru @LogArgs}
$SbNoEnable = {Import-Pog -PassThru @Params | Install-Pog -PassThru:$PassThru @LogArgs}

$Sb = if ($InstallOnly) {$SbNoEnable}
elseif ($NoExport) {$SbNoExport}
else {$SbAll}

$sp = $Sb.GetSteppablePipeline()
$sp.Begin($PSCmdlet)
}

process {
$sp.Process($_)
}

end {
$sp.End()
}
}


<# Ad-hoc template format used to create default manifests in the following 2 functions. #>
function RenderTemplate($SrcPath, $DestinationPath, [Hashtable]$TemplateData) {
$Template = Get-Content -Raw $SrcPath
Expand Down
12 changes: 7 additions & 5 deletions app/Pog/lib_compiled/Pog/src/Commands/ImportPogCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public sealed class ImportPogCommand : PackageCommandBase {
// Import-Pog -TargetName [-TargetPackageRoot]
private const string TargetName_PS = "_TargetName";

internal const string DefaultPS = PackageName_PS;

// split parameter set into flags for each possible value
[Flags]
private enum PS {
Expand Down Expand Up @@ -270,11 +272,6 @@ private static ImportedPackage GetTargetPackage(string packageName, string? pack
}

private void ImportPackage(RepositoryPackage package, ImportedPackage target) {
var actionStr = $"Importing {package.GetDescriptionString()} to '{target.Path}'.";
if (!ShouldProcess(actionStr, actionStr, null)) {
return;
}

// TODO: in the PowerShell version, we used to run Confirm-PogRepositoryManifest here;
// think through whether it's a good idea to add that back

Expand All @@ -286,6 +283,11 @@ private void ImportPackage(RepositoryPackage package, ImportedPackage target) {
return;
}

var actionStr = $"Importing {package.GetDescriptionString()} to '{target.Path}'.";
if (!ShouldProcess(actionStr, actionStr, null)) {
return;
}

if (!ConfirmManifestOverwrite(package, target)) {
WriteInformation($"Skipping import of package '{package.PackageName}'.");
return;
Expand Down
113 changes: 113 additions & 0 deletions app/Pog/lib_compiled/Pog/src/Commands/InvokePogCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Collections;
using System.Management.Automation;
using System.Reflection;
using JetBrains.Annotations;
using Pog.Commands.Common;

namespace Pog.Commands;

// TODO: allow wildcards in PackageName and Version arguments for commands where it makes sense

/// <summary>
/// <para type="synopsis">Import, install, enable and export a package.</para>
/// <para type="description">
/// Runs all four installation stages in order. All arguments passed to this cmdlet except for <c>-InstallOnly</c> and
/// <c>-NoExport</c> are forwarded to <c>Import-Pog</c>.
/// </para>
/// </summary>
[PublicAPI]
[Alias("pog")]
// Cmdlet(...) params are manually copied from Import-Pog, there doesn't seem any way to dynamically copy this like with dynamicparam
[Cmdlet(VerbsLifecycle.Invoke, "Pog", DefaultParameterSetName = ImportPogCommand.DefaultPS)]
[OutputType(typeof(ImportedPackage))]
public sealed class InvokePogCommand : PackageCommandBase, IDynamicParameters {
/// <summary><para type="description">
/// Import and install the package, do not enable and export.
/// </para></summary>
[Parameter] public SwitchParameter Install;

/// <summary><para type="description">
/// Import, install and enable the package, do not export it.
/// </para></summary>
[Parameter] public SwitchParameter Enable;

// TODO: add an `-Imported` parameter set to allow installing+enabling+exporting an imported package

private static readonly CommandInfo ImportPogInfo = new CmdletInfo("Import-Pog", typeof(ImportPogCommand));
private static readonly CommandInfo InstallPogInfo = new CmdletInfo("Install-Pog", typeof(InstallPogCommand));
private static readonly CommandInfo EnablePogInfo = new CmdletInfo("Enable-Pog", typeof(EnablePogCommand));
private static readonly CommandInfo ExportPogInfo = new CmdletInfo("Export-Pog", typeof(ExportPogCommand));

private DynamicCommandParameters? _proxiedParams;
private PowerShell _ps = PowerShell.Create();
private SteppablePipeline? _pipeline;

public object GetDynamicParameters() {
if (_proxiedParams != null) {
return _proxiedParams;
}
var builder = new DynamicCommandParameters.Builder(UnknownAttributeHandler: (paramName, attr) =>
throw new InternalError($"Cannot copy parameter '{paramName}', attribute '{attr.GetType()}'."));
return _proxiedParams = builder.CopyParameters(ImportPogInfo);
}

// TODO: rollback on error
protected override void BeginProcessing() {
base.BeginProcessing();

var importParams = _proxiedParams!.Extract();

// reuse PassThru parameter from Import-Pog for Enable-Pog
var passThru = (importParams["PassThru"] as SwitchParameter?)?.IsPresent ?? false;
importParams.Remove("PassThru");

// TODO: check if these aren't forwarded automatically, alternatively expand to all common parameters
var logArgs = new Hashtable();
if (MyInvocation.BoundParameters.ContainsKey("Verbose")) {
logArgs["Verbose"] = MyInvocation.BoundParameters["Verbose"];
}
if (MyInvocation.BoundParameters.ContainsKey("Debug")) {
logArgs["Debug"] = MyInvocation.BoundParameters["Debug"];
}

_ps.AddCommand(ImportPogInfo).AddParameters(logArgs).AddParameter("PassThru").AddParameters(importParams);
if (Install) {
_ps.AddCommand(InstallPogInfo).AddParameters(logArgs).AddParameter("PassThru", passThru);
} else {
_ps.AddCommand(InstallPogInfo).AddParameters(logArgs).AddParameter("PassThru");
if (Enable) {
_ps.AddCommand(EnablePogInfo).AddParameters(logArgs).AddParameter("PassThru", passThru);
} else {
_ps.AddCommand(EnablePogInfo).AddParameters(logArgs).AddParameter("PassThru");
_ps.AddCommand(ExportPogInfo).AddParameters(logArgs).AddParameter("PassThru", passThru);
}
}

_pipeline = (SteppablePipeline) PowerShellGetSteppablePipelineMethod.Invoke(_ps, []);
_pipeline.Begin(this);
}

protected override void ProcessRecord() {
base.ProcessRecord();

// https://github.com/orgs/PowerShell/discussions/21356
var value = CurrentPipelineObjectProperty.GetValue(this);
_pipeline!.Process(value);
}

protected override void EndProcessing() {
base.EndProcessing();
_pipeline!.End();
}

public override void Dispose() {
_ps.Dispose();
_pipeline?.Dispose();
}

private static readonly MethodInfo PowerShellGetSteppablePipelineMethod = typeof(PowerShell).GetMethod(
"GetSteppablePipeline", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, [], [])!;

private static readonly PropertyInfo CurrentPipelineObjectProperty = typeof(PSCmdlet).GetProperty(
"CurrentPipelineObject", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!;
}

0 comments on commit 377d18b

Please sign in to comment.