From 9b59f4c70e2309fbcd8343d65d0731203737c3b9 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 14 Jan 2022 12:06:34 -0800 Subject: [PATCH 1/8] Complete implementaion of RequiredResourceFile and RequiredResource params --- src/code/InstallPSResource.cs | 252 +++++++++++++++++++++++++++++++--- src/code/InstallPkgParams.cs | 97 +++++++++++++ 2 files changed, 332 insertions(+), 17 deletions(-) create mode 100644 src/code/InstallPkgParams.cs diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 88fbde63d..3a7174002 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -2,9 +2,12 @@ // Licensed under the MIT License. using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using Newtonsoft.Json; using NuGet.Versioning; using System; +using System.Collections; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Management.Automation; @@ -90,7 +93,7 @@ class InstallPSResource : PSCmdlet /// /// Prevents installing a package that contains cmdlets that already exist on the machine. /// - [Parameter] + [Parameter] public SwitchParameter NoClobber { get; set; } /// @@ -113,6 +116,62 @@ class InstallPSResource : PSCmdlet [ValidateNotNullOrEmpty] public PSResourceInfo InputObject { get; set; } + /// + /// Installs resources based on input from a JSON file. + /// + [Parameter(ParameterSetName = RequiredResourceFileParameterSet)] + [ValidateNotNullOrEmpty] + public String RequiredResourceFile + { + get { + return _requiredResourceFile; + } + set + { + string resolvedPath = string.Empty; + if (!string.IsNullOrWhiteSpace(value)) + { + resolvedPath = SessionState.Path.GetResolvedPSPathFromPSPath(value).First().Path; + } + + if (!File.Exists(resolvedPath)) + { + var exMessage = String.Format("The RequiredResourceFile does not exist. Please try specifying a path to a valid .json or .psd1 file"); + var ex = new ArgumentException(exMessage); + var RequiredResourceFileDoesNotExist = new ErrorRecord(ex, "RequiredResourceFileDoesNotExist", ErrorCategory.ObjectNotFound, null); + + ThrowTerminatingError(RequiredResourceFileDoesNotExist); + } + + _requiredResourceFile = resolvedPath; + } + } + + /// + /// Installs resources in a hashtable or JSON string format. + /// + [Parameter(ParameterSetName = RequiredResourceParameterSet)] + public Object RequiredResource // takes either string (json) or hashtable + { + get { return _requiredResourceHash != null ? (Object)_requiredResourceHash : (Object)_requiredResourceJson; } + + set + { + if (value.GetType().Name.Equals("String")) + { + _requiredResourceJson = (String)value; + } + else if (value.GetType().Name.Equals("Hashtable")) + { + _requiredResourceHash = (Hashtable)value; + } + else + { + throw new ParameterBindingException("Object is not a JSON or Hashtable"); + } + } + } + #endregion #region Members @@ -122,6 +181,9 @@ class InstallPSResource : PSCmdlet private const string RequiredResourceFileParameterSet = "RequiredResourceFileParameterSet"; private const string RequiredResourceParameterSet = "RequiredResourceParameterSet"; List _pathsToInstallPkg; + private string _requiredResourceFile; + private string _requiredResourceJson; + private Hashtable _requiredResourceHash; VersionRange _versionRange; InstallHelper _installHelper; @@ -161,8 +223,11 @@ protected override void ProcessRecord() ProcessInstallHelper( pkgNames: Name, + pkgVersion: _versionRange, pkgPrerelease: Prerelease, - pkgRepository: Repository); + pkgRepository: Repository, + pkgCredential: Credential, + reqResourceParams: null); break; case InputObjectParameterSet: @@ -177,24 +242,98 @@ protected override void ProcessRecord() ProcessInstallHelper( pkgNames: new string[] { InputObject.Name }, + pkgVersion: _versionRange, pkgPrerelease: InputObject.IsPrerelease, - pkgRepository: new string[]{ InputObject.Repository }); + pkgRepository: new string[]{ InputObject.Repository }, + pkgCredential: Credential, + reqResourceParams: null); break; case RequiredResourceFileParameterSet: - ThrowTerminatingError(new ErrorRecord( - new PSNotImplementedException("RequiredResourceFileParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), - "CommandParameterSetNotImplementedYet", - ErrorCategory.NotImplemented, - this)); + /* json file contents should look like: + { + "Pester": { + "allowPrerelease": true, + "version": "[4.4.2,4.7.0]", + "repository": "PSGallery", + "credential": null + } + } + */ + + string requiredResourceFileStream = string.Empty; + using (StreamReader sr = new StreamReader(_requiredResourceFile)) + { + requiredResourceFileStream = sr.ReadToEnd(); + } + + Hashtable pkgsInJsonFile = null; + try + { + pkgsInJsonFile = JsonConvert.DeserializeObject(requiredResourceFileStream, new JsonSerializerSettings { MaxDepth = 6 }); + + } + catch (Exception e) + { + var exMessage = String.Format("Argument for parameter -RequiredResourceFile is not in proper json format. Make sure argument is either a valid json file."); + var ex = new ArgumentException(exMessage); + var RequiredResourceFileNotInProperJsonFormat = new ErrorRecord(ex, "RequiredResourceFileNotInProperJsonFormat", ErrorCategory.InvalidData, null); + + ThrowTerminatingError(RequiredResourceFileNotInProperJsonFormat); + } + + RequiredResourceHelper(pkgsInJsonFile); break; case RequiredResourceParameterSet: - ThrowTerminatingError(new ErrorRecord( - new PSNotImplementedException("RequiredResourceParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), - "CommandParameterSetNotImplementedYet", - ErrorCategory.NotImplemented, - this)); + if (!string.IsNullOrWhiteSpace(_requiredResourceJson)) + { + /* json would look like: + { + "Pester": { + "allowPrerelease": true, + "version": "[4.4.2,4.7.0]", + "repository": "PSGallery", + "credential": null + } + } + */ + + Hashtable pkgsInJson = null; + try + { + //pkgsInJson = JsonConvert.DeserializeObject>(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); + pkgsInJson = JsonConvert.DeserializeObject(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); + + } + catch (Exception e) + { + var exMessage = String.Format("Argument for parameter -RequiredResource is not in proper json format. Make sure argument is either a valid json file."); + var ex = new ArgumentException(exMessage); + var RequiredResourceFileNotInProperJsonFormat = new ErrorRecord(ex, "RequiredResourceFileNotInProperJsonFormat", ErrorCategory.InvalidData, null); + + ThrowTerminatingError(RequiredResourceFileNotInProperJsonFormat); + } + + RequiredResourceHelper(pkgsInJson); + } + + if (_requiredResourceHash != null) + { + /* hashtable would look like: + @{ + "Configuration" = @{ version = "[4.4.2,4.7.0]" } + "Pester" = @{ + version = "[4.4.2,4.7.0]" + repository = PSGallery + credential = $cred + prerelease = $true + } + } + */ + + RequiredResourceHelper(_requiredResourceHash); + } break; default: @@ -202,12 +341,91 @@ protected override void ProcessRecord() break; } } - + #endregion #region Methods - private void ProcessInstallHelper(string[] pkgNames, bool pkgPrerelease, string[] pkgRepository) + private void RequiredResourceHelper(Hashtable reqResourceHash) + { + var pkgNames = reqResourceHash.Keys; + + foreach (string pkgName in pkgNames) + { + var pkgParamInfo = reqResourceHash[pkgName]; + Hashtable pkgInstallInfo = new Hashtable { }; + + if (pkgParamInfo is Hashtable) + { + // Input format was Hashtable + pkgInstallInfo = pkgParamInfo as Hashtable; + } + else + { + // Input format was JSON + var pkgParamJTokens = pkgParamInfo as Newtonsoft.Json.Linq.JToken; + foreach (Newtonsoft.Json.Linq.JProperty val in pkgParamJTokens) + { + pkgInstallInfo[val.Name] = val.Value.ToString(); + } + } + + if (pkgInstallInfo == null) + { + return; + } + + InstallPkgParams pkgParams = new InstallPkgParams(); + var pkgParamNames = pkgInstallInfo.Keys; + + PSCredential pkgCredential = Credential; + foreach (string paramName in pkgParamNames) + { + if (string.Equals(paramName, "credential", StringComparison.InvariantCultureIgnoreCase)) + { + WriteVerbose("Credential specified for required resource"); + pkgCredential = pkgInstallInfo[paramName] as PSCredential; + } + + pkgParams.SetProperty(paramName, pkgInstallInfo[paramName] as string, out ErrorRecord IncorrectVersionFormat); + + if (IncorrectVersionFormat != null) + { + ThrowTerminatingError(IncorrectVersionFormat); + } + } + + if (pkgParams.Scope == ScopeType.AllUsers) + { + _pathsToInstallPkg = Utils.GetAllInstallationPaths(this, pkgParams.Scope); + } + + VersionRange pkgVersion; + // If no Version specified, install latest version for the package. + // Otherwise validate Version can be parsed out successfully. + if (pkgInstallInfo["version"] == null || string.IsNullOrWhiteSpace(pkgInstallInfo["version"].ToString())) + { + pkgVersion = VersionRange.All; + } + else if (!Utils.TryParseVersionOrVersionRange(pkgInstallInfo["version"].ToString(), out pkgVersion)) + { + var exMessage = "Argument for Version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + var IncorrectVersionFormat = new ErrorRecord(ex, "IncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + ThrowTerminatingError(IncorrectVersionFormat); + } + + ProcessInstallHelper( + pkgNames: new string[] { pkgName }, + pkgVersion: pkgVersion, + pkgPrerelease: pkgParams.Prerelease, + pkgRepository: new string[] { pkgParams.Repository }, + pkgCredential: pkgCredential, + reqResourceParams: pkgParams); + } + } + + private void ProcessInstallHelper(string[] pkgNames, VersionRange pkgVersion, bool pkgPrerelease, string[] pkgRepository, PSCredential pkgCredential, InstallPkgParams reqResourceParams) { var inputNameToInstall = Utils.ProcessNameWildcards(pkgNames, out string[] errorMsgs, out bool nameContainsWildcard); if (nameContainsWildcard) @@ -244,7 +462,7 @@ private void ProcessInstallHelper(string[] pkgNames, bool pkgPrerelease, string[ var installedPkgs = _installHelper.InstallPackages( names: pkgNames, - versionRange: _versionRange, + versionRange: pkgVersion, prerelease: pkgPrerelease, repository: pkgRepository, acceptLicense: AcceptLicense, @@ -253,7 +471,7 @@ private void ProcessInstallHelper(string[] pkgNames, bool pkgPrerelease, string[ force: false, trustRepository: TrustRepository, noClobber: NoClobber, - credential: Credential, + credential: pkgCredential, asNupkg: false, includeXML: true, skipDependencyCheck: SkipDependencyCheck, diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs new file mode 100644 index 000000000..4be24b7ac --- /dev/null +++ b/src/code/InstallPkgParams.cs @@ -0,0 +1,97 @@ +using Microsoft.PowerShell.PowerShellGet.UtilClasses; +using NuGet.Versioning; +using System; +using System.Collections; +using System.Management.Automation; + +public class InstallPkgParams +{ + public string Name { get; set; } + public VersionRange Version { get; set; } + public string Repository { get; set; } + public bool AcceptLicense { get; set; } + public bool Prerelease { get; set; } + public ScopeType Scope { get; set; } + public bool Quiet { get; set; } + public bool Reinstall { get; set; } + public bool Force { get; set; } + public bool TrustRepository { get; set; } + public bool NoClobber { get; set; } + public bool SkipDependencyCheck { get; set; } + + + #region Private methods + + public void SetProperty(string propertyName, string propertyValue, out ErrorRecord IncorrectVersionFormat) + { + IncorrectVersionFormat = null; + switch (propertyName.ToLower()) + { + case "name": + Name = propertyValue.Trim(); + break; + + case "version": + // If no Version specified, install latest version for the package. + // Otherwise validate Version can be parsed out successfully. + VersionRange versionTmp = VersionRange.None; + if (string.IsNullOrWhiteSpace(propertyValue)) + { + Version = VersionRange.All; + } + else if (!Utils.TryParseVersionOrVersionRange(propertyValue, out versionTmp)) + { + var exMessage = "Argument for Version parameter is not in the proper format."; + var ex = new ArgumentException(exMessage); + IncorrectVersionFormat = new ErrorRecord(ex, "IncorrectVersionFormat", ErrorCategory.InvalidArgument, null); + } + Version = versionTmp; + break; + + case "repository": + Repository = propertyValue.Trim(); + break; + + case "acceptlicense": + bool.TryParse(propertyValue, out bool acceptLicenseTmp); + AcceptLicense = acceptLicenseTmp; + break; + + case "prerelease": + bool.TryParse(propertyValue, out bool prereleaseTmp); + Prerelease = prereleaseTmp; + break; + + case "scope": + ScopeType scope = (ScopeType)Enum.Parse(typeof(ScopeType), propertyValue, ignoreCase: true); + Scope = scope; + break; + + case "quiet": + bool.TryParse(propertyValue, out bool quietTmp); + Quiet = quietTmp; + break; + + case "reinstall": + bool.TryParse(propertyValue, out bool reinstallTmp); + Reinstall = reinstallTmp; + break; + + case "trustrepository": + bool.TryParse(propertyValue, out bool trustRepositoryTmp); + TrustRepository = trustRepositoryTmp; + break; + + case "noclobber": + bool.TryParse(propertyValue, out bool noClobberTmp); + NoClobber = noClobberTmp; + break; + + case "skipdependencycheck": + bool.TryParse(propertyValue, out bool skipDependencyCheckTmp); + SkipDependencyCheck = skipDependencyCheckTmp; + break; + } + } + #endregion +} \ No newline at end of file From 5aa5e81f138e7724a8bb1b176f5a9f488467caab Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 14 Jan 2022 12:06:57 -0800 Subject: [PATCH 2/8] Add tests for RequiredResourceFile and RequiredResource params --- test/InstallPSResource.Tests.ps1 | 81 ++++++++++++++++++++++++++++++ test/TestRequiredResourceFile.json | 13 +++++ 2 files changed, 94 insertions(+) create mode 100644 test/TestRequiredResourceFile.json diff --git a/test/InstallPSResource.Tests.ps1 b/test/InstallPSResource.Tests.ps1 index b5cf93604..bf73e0fb2 100644 --- a/test/InstallPSResource.Tests.ps1 +++ b/test/InstallPSResource.Tests.ps1 @@ -11,6 +11,7 @@ Describe 'Test Install-PSResource for Module' { $PSGalleryName = Get-PSGalleryName $NuGetGalleryName = Get-NuGetGalleryName $testModuleName = "TestModule" + $RequiredResourceJSONFileName = "TestRequiredResourceFile.json" Get-NewPSResourceRepositoryFile Register-LocalRepos } @@ -317,6 +318,86 @@ Describe 'Test Install-PSResource for Module' { $res.Name | Should -Be "TestModule" $res.Version | Should -Be "1.3.0.0" } + + It "Install modules using -RequiredResource with hashtable" { + $rrHash = @{ + TestModule = @{ + version = "[0.0.1,1.3.0]" + repository = $TestGalleryName + } + + TestModulePrerelease = @{ + version = "[0.0.0,0.0.5]" + repository = $TestGalleryName + prerelease = "true" + } + + TestModule99 = @{ + } + } + + Install-PSResource -RequiredResource $rrHash + + $res1 = Get-Module "TestModule" -ListAvailable + $res1.Name | Should -Be "TestModule" + $res1.Version | Should -Be "1.3.0" + + $res2 = Get-Module "TestModulePrerelease" -ListAvailable + $res2.Name | Should -Be "TestModulePrerelease" + $res2.Version | Should -Be "0.0.1" + + $res3 = Get-Module "TestModule99" -ListAvailable + $res3.Name | Should -Be "TestModule99" + $res3.Version | Should -Be "0.0.5" + } + + It "Install modules using -RequiredResource with JSON string" { + $rrJSON = "{ + 'TestModule': { + 'version': '[0.0.1,1.3.0]', + 'repository': 'PoshTestGallery' + }, + 'TestModulePrerelease': { + 'version': '[0.0.0,0.0.5]', + 'repository': 'PoshTestGallery', + 'prerelease': 'true' + }, + 'TestModule99': { + } + }" + + Install-PSResource -RequiredResource $rrJSON + + $res1 = Get-Module "TestModule" -ListAvailable + $res1.Name | Should -Be "TestModule" + $res1.Version | Should -Be "1.3.0" + + $res2 = Get-Module "TestModulePrerelease" -ListAvailable + $res2.Name | Should -Be "TestModulePrerelease" + $res2.Version | Should -Be "0.0.1" + + $res3 = Get-Module "TestModule99" -ListAvailable + $res3.Name | Should -Be "TestModule99" + $res3.Version | Should -Be "0.0.5" + } + + It "Install modules using -RequiredResourceFile with JSON file" { + $rrFileJSON = ".\$RequiredResourceJSONFileName" + + Install-PSResource -RequiredResourceFile $rrFileJSON -Verbose + + $res1 = Get-Module "TestModule" -ListAvailable + $res1.Name | Should -Be "TestModule" + $res1.Version | Should -Be "1.2.0" + + $res2 = Get-Module "TestModulePrerelease" -ListAvailable + $res2.Name | Should -Be "TestModulePrerelease" + $res2.Version | Should -Be "0.0.1" + + $res3 = Get-Module "TestModule99" -ListAvailable + $res3.Name | Should -Be "TestModule99" + $res3.Version | Should -Be "0.0.5" + } } <# Temporarily commented until -Tag is implemented for this Describe block diff --git a/test/TestRequiredResourceFile.json b/test/TestRequiredResourceFile.json new file mode 100644 index 000000000..1a790c79b --- /dev/null +++ b/test/TestRequiredResourceFile.json @@ -0,0 +1,13 @@ +{ + "TestModule": { + "version": "[0.0.1,1.3.0)", + "repository": "PoshTestGallery" + }, + "TestModulePrerelease": { + "version": "[0.0.0,0.0.5]", + "repository": "PoshTestGallery", + "prerelease": "true" + }, + "TestModule99": { + } +} From 1bc46affc15fe075bff23148b8dd8e6cb3d7c6de Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 14 Jan 2022 12:35:51 -0800 Subject: [PATCH 3/8] Add header to file --- src/code/InstallPkgParams.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs index 4be24b7ac..bf4623f48 100644 --- a/src/code/InstallPkgParams.cs +++ b/src/code/InstallPkgParams.cs @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + using Microsoft.PowerShell.PowerShellGet.UtilClasses; using NuGet.Versioning; using System; From b5dcfbb6fae1c84b0a70bdf633aac8ea1f443d64 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 14 Jan 2022 12:49:20 -0800 Subject: [PATCH 4/8] Remove unused variables --- src/code/InstallPSResource.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 3a7174002..9f9af30f9 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -273,7 +273,7 @@ protected override void ProcessRecord() pkgsInJsonFile = JsonConvert.DeserializeObject(requiredResourceFileStream, new JsonSerializerSettings { MaxDepth = 6 }); } - catch (Exception e) + catch (Exception) { var exMessage = String.Format("Argument for parameter -RequiredResourceFile is not in proper json format. Make sure argument is either a valid json file."); var ex = new ArgumentException(exMessage); @@ -306,7 +306,7 @@ protected override void ProcessRecord() pkgsInJson = JsonConvert.DeserializeObject(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); } - catch (Exception e) + catch (Exception) { var exMessage = String.Format("Argument for parameter -RequiredResource is not in proper json format. Make sure argument is either a valid json file."); var ex = new ArgumentException(exMessage); From da6c7d97f5b4c21dc0633ccec918bf42f2af1530 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Fri, 14 Jan 2022 17:28:38 -0800 Subject: [PATCH 5/8] Update src/code/InstallPkgParams.cs Co-authored-by: Paul Higinbotham --- src/code/InstallPkgParams.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs index bf4623f48..8d5f5e55d 100644 --- a/src/code/InstallPkgParams.cs +++ b/src/code/InstallPkgParams.cs @@ -20,7 +20,8 @@ public class InstallPkgParams public bool Force { get; set; } public bool TrustRepository { get; set; } public bool NoClobber { get; set; } - public bool SkipDependencyCheck { get; set; } +public bool SkipDependencyCheck { get; set; } + #region Private methods From b048537db16b5f619e81808d1bdcb1b88ec39a75 Mon Sep 17 00:00:00 2001 From: alerickson <25858831+alerickson@users.noreply.github.com> Date: Tue, 18 Jan 2022 19:58:26 -0800 Subject: [PATCH 6/8] Update src/code/InstallPSResource.cs Co-authored-by: Paul Higinbotham --- src/code/InstallPSResource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 9f9af30f9..68349e02e 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -123,7 +123,8 @@ class InstallPSResource : PSCmdlet [ValidateNotNullOrEmpty] public String RequiredResourceFile { - get { + get + { return _requiredResourceFile; } set From 9c0a24fd950c43a2ab32554a138dd7ace24941e5 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Tue, 18 Jan 2022 21:29:52 -0800 Subject: [PATCH 7/8] Incorporate some feedback into PR --- src/code/InstallPSResource.cs | 40 ++++++++++++++++------------------- src/code/InstallPkgParams.cs | 6 +++++- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index 68349e02e..e4c97a9cd 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -137,7 +137,7 @@ public String RequiredResourceFile if (!File.Exists(resolvedPath)) { - var exMessage = String.Format("The RequiredResourceFile does not exist. Please try specifying a path to a valid .json or .psd1 file"); + var exMessage = String.Format("The RequiredResourceFile does not exist. Please try specifying a path to a valid .json file"); var ex = new ArgumentException(exMessage); var RequiredResourceFileDoesNotExist = new ErrorRecord(ex, "RequiredResourceFileDoesNotExist", ErrorCategory.ObjectNotFound, null); @@ -154,17 +154,17 @@ public String RequiredResourceFile [Parameter(ParameterSetName = RequiredResourceParameterSet)] public Object RequiredResource // takes either string (json) or hashtable { - get { return _requiredResourceHash != null ? (Object)_requiredResourceHash : (Object)_requiredResourceJson; } + get { return _requiredResourceHash != null ? _requiredResourceHash : (Object)_requiredResourceJson; } set { - if (value.GetType().Name.Equals("String")) + if (value is String jsonResource) { - _requiredResourceJson = (String)value; + _requiredResourceJson = jsonResource; } - else if (value.GetType().Name.Equals("Hashtable")) + else if (value is Hashtable hashResource) { - _requiredResourceHash = (Hashtable)value; + _requiredResourceHash = hashResource; } else { @@ -300,12 +300,10 @@ protected override void ProcessRecord() } */ - Hashtable pkgsInJson = null; + Hashtable pkgsHash = null; try { - //pkgsInJson = JsonConvert.DeserializeObject>(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); - pkgsInJson = JsonConvert.DeserializeObject(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); - + pkgsHash = JsonConvert.DeserializeObject(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); } catch (Exception) { @@ -316,7 +314,7 @@ protected override void ProcessRecord() ThrowTerminatingError(RequiredResourceFileNotInProperJsonFormat); } - RequiredResourceHelper(pkgsInJson); + RequiredResourceHelper(pkgsHash); } if (_requiredResourceHash != null) @@ -353,23 +351,21 @@ private void RequiredResourceHelper(Hashtable reqResourceHash) foreach (string pkgName in pkgNames) { - var pkgParamInfo = reqResourceHash[pkgName]; - Hashtable pkgInstallInfo = new Hashtable { }; - - if (pkgParamInfo is Hashtable) - { - // Input format was Hashtable - pkgInstallInfo = pkgParamInfo as Hashtable; - } - else - { - // Input format was JSON + var pkgParamInfo = reqResourceHash[pkgName]; + + // Input format was json, and not a hashtable + if (!(pkgParamInfo is Hashtable pkgInstallInfo)) + { + pkgInstallInfo = new Hashtable { }; + + // Input format was JSON var pkgParamJTokens = pkgParamInfo as Newtonsoft.Json.Linq.JToken; foreach (Newtonsoft.Json.Linq.JProperty val in pkgParamJTokens) { pkgInstallInfo[val.Name] = val.Value.ToString(); } } + // Otherwise, the input format is a hashtable if (pkgInstallInfo == null) { diff --git a/src/code/InstallPkgParams.cs b/src/code/InstallPkgParams.cs index 8d5f5e55d..20000a512 100644 --- a/src/code/InstallPkgParams.cs +++ b/src/code/InstallPkgParams.cs @@ -20,7 +20,7 @@ public class InstallPkgParams public bool Force { get; set; } public bool TrustRepository { get; set; } public bool NoClobber { get; set; } -public bool SkipDependencyCheck { get; set; } + public bool SkipDependencyCheck { get; set; } @@ -95,6 +95,10 @@ public void SetProperty(string propertyName, string propertyValue, out ErrorReco bool.TryParse(propertyValue, out bool skipDependencyCheckTmp); SkipDependencyCheck = skipDependencyCheckTmp; break; + + default: + // write error + break; } } #endregion From 317fa59e3ec10f960ff6ae944bb0487c93ae6f89 Mon Sep 17 00:00:00 2001 From: Amber Erickson Date: Fri, 21 Jan 2022 14:11:51 -0800 Subject: [PATCH 8/8] Add method to convert json to hashtable (from SecretManagement) --- src/code/InstallPSResource.cs | 25 ++++---------- src/code/Utils.cs | 62 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/code/InstallPSResource.cs b/src/code/InstallPSResource.cs index e4c97a9cd..9282142ba 100644 --- a/src/code/InstallPSResource.cs +++ b/src/code/InstallPSResource.cs @@ -10,6 +10,7 @@ using System.IO; using System.Linq; using System.Management.Automation; +using Microsoft.PowerShell.Commands; using Dbg = System.Diagnostics.Debug; @@ -271,8 +272,7 @@ protected override void ProcessRecord() Hashtable pkgsInJsonFile = null; try { - pkgsInJsonFile = JsonConvert.DeserializeObject(requiredResourceFileStream, new JsonSerializerSettings { MaxDepth = 6 }); - + pkgsInJsonFile = Utils.ConvertJsonToHashtable(this, requiredResourceFileStream); } catch (Exception) { @@ -303,7 +303,7 @@ protected override void ProcessRecord() Hashtable pkgsHash = null; try { - pkgsHash = JsonConvert.DeserializeObject(_requiredResourceJson, new JsonSerializerSettings { MaxDepth = 6 }); + pkgsHash = Utils.ConvertJsonToHashtable(this, _requiredResourceJson); } catch (Exception) { @@ -351,23 +351,10 @@ private void RequiredResourceHelper(Hashtable reqResourceHash) foreach (string pkgName in pkgNames) { - var pkgParamInfo = reqResourceHash[pkgName]; - - // Input format was json, and not a hashtable - if (!(pkgParamInfo is Hashtable pkgInstallInfo)) - { - pkgInstallInfo = new Hashtable { }; - - // Input format was JSON - var pkgParamJTokens = pkgParamInfo as Newtonsoft.Json.Linq.JToken; - foreach (Newtonsoft.Json.Linq.JProperty val in pkgParamJTokens) - { - pkgInstallInfo[val.Name] = val.Value.ToString(); - } - } - // Otherwise, the input format is a hashtable + var pkgParamInfo = reqResourceHash[pkgName]; - if (pkgInstallInfo == null) + // Format should now be a hashtable, whether the original input format was json or hashtable + if (!(pkgParamInfo is Hashtable pkgInstallInfo)) { return; } diff --git a/src/code/Utils.cs b/src/code/Utils.cs index 2b5e5b9aa..be3738bf3 100644 --- a/src/code/Utils.cs +++ b/src/code/Utils.cs @@ -5,10 +5,12 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Management.Automation; using System.Management.Automation.Language; +using System.Management.Automation.Runspaces; using System.Runtime.InteropServices; namespace Microsoft.PowerShell.PowerShellGet.UtilClasses @@ -19,6 +21,47 @@ internal static class Utils public static readonly string[] EmptyStrArray = Array.Empty(); + private const string ConvertJsonToHashtableScript = @" + param ( + [string] $json + ) + + function ConvertToHash + { + param ( + [pscustomobject] $object + ) + + $output = @{} + $object | Microsoft.PowerShell.Utility\Get-Member -MemberType NoteProperty | ForEach-Object { + $name = $_.Name + $value = $object.($name) + + if ($value -is [object[]]) + { + $array = @() + $value | ForEach-Object { + $array += (ConvertToHash $_) + } + $output.($name) = $array + } + elseif ($value -is [pscustomobject]) + { + $output.($name) = (ConvertToHash $value) + } + else + { + $output.($name) = $value + } + } + + $output + } + + $customObject = Microsoft.PowerShell.Utility\ConvertFrom-Json -InputObject $json + return ConvertToHash $customObject + "; + #endregion #region String methods @@ -503,6 +546,25 @@ public static void WriteVerboseOnCmdlet( catch { } } + /// + /// Convert a json string into a hashtable object. + /// This uses custom script to perform the PSObject -> Hashtable + /// conversion, so that this works with WindowsPowerShell. + /// + public static Hashtable ConvertJsonToHashtable( + PSCmdlet cmdlet, + string json) + { + Collection results = cmdlet.InvokeCommand.InvokeScript( + script: ConvertJsonToHashtableScript, + useNewScope: true, + writeToPipeline: PipelineResultTypes.Error, + input: null, + args: new object[] { json }); + + return (results.Count == 1 && results[0] != null) ? (Hashtable)results[0].BaseObject : null; + } + #endregion #region Directory and File