From 377d18b2771fbcc43f705979fab5be35ccba90d8 Mon Sep 17 00:00:00 2001 From: Matej Kafka Date: Fri, 22 Mar 2024 00:06:31 +0100 Subject: [PATCH] Invoke-Pog: WIP port to C# --- app/Pog/Pog.psd1 | 3 +- app/Pog/Pog.psm1 | 69 +---------- .../Pog/src/Commands/ImportPogCommand.cs | 12 +- .../Pog/src/Commands/InvokePogCommand.cs | 113 ++++++++++++++++++ 4 files changed, 123 insertions(+), 74 deletions(-) create mode 100644 app/Pog/lib_compiled/Pog/src/Commands/InvokePogCommand.cs diff --git a/app/Pog/Pog.psd1 b/app/Pog/Pog.psd1 index 6d89bdb..aa12ee5 100644 --- a/app/Pog/Pog.psd1 +++ b/app/Pog/Pog.psd1 @@ -13,6 +13,7 @@ AliasesToExport = @('pog') CmdletsToExport = @( + 'Invoke-Pog' 'Import-Pog' 'Install-Pog' 'Enable-Pog' @@ -32,8 +33,6 @@ ) FunctionsToExport = @( - 'Invoke-Pog' - 'Update-PogManifest' 'New-PogPackage' 'New-PogImportedPackage' diff --git a/app/Pog/Pog.psm1 b/app/Pog/Pog.psm1 index e7fc7e3..274349b 100644 --- a/app/Pog/Pog.psm1 +++ b/app/Pog/Pog.psm1 @@ -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 @@ -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 diff --git a/app/Pog/lib_compiled/Pog/src/Commands/ImportPogCommand.cs b/app/Pog/lib_compiled/Pog/src/Commands/ImportPogCommand.cs index 519c06c..4af010d 100644 --- a/app/Pog/lib_compiled/Pog/src/Commands/ImportPogCommand.cs +++ b/app/Pog/lib_compiled/Pog/src/Commands/ImportPogCommand.cs @@ -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 { @@ -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 @@ -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; diff --git a/app/Pog/lib_compiled/Pog/src/Commands/InvokePogCommand.cs b/app/Pog/lib_compiled/Pog/src/Commands/InvokePogCommand.cs new file mode 100644 index 0000000..d4a0588 --- /dev/null +++ b/app/Pog/lib_compiled/Pog/src/Commands/InvokePogCommand.cs @@ -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 + +/// +/// Import, install, enable and export a package. +/// +/// Runs all four installation stages in order. All arguments passed to this cmdlet except for -InstallOnly and +/// -NoExport are forwarded to Import-Pog. +/// +/// +[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 { + /// + /// Import and install the package, do not enable and export. + /// + [Parameter] public SwitchParameter Install; + + /// + /// Import, install and enable the package, do not export it. + /// + [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)!; +}