diff --git a/help/Find-PSResource.md b/help/Find-PSResource.md index cac8dfa5b..82fbce245 100644 --- a/help/Find-PSResource.md +++ b/help/Find-PSResource.md @@ -19,13 +19,13 @@ Searches for packages from a repository (local or remote), based on `-Name` and ### CommandNameParameterSet ``` PowerShell -[[-CommandName] ] [-ModuleName ] [-Version ] [-Prerelease] [-Tag ] +[[-CommandName] ] [-ModuleName ] [-Version ] [-Prerelease] [-Tag ] [-Repository ] [-Credential ] [-IncludeDependencies] [-WhatIf] [-Confirm] [] ``` ### DscResourceNameParameterSet ``` PowerShell -[[-DscResourceName] ] [-ModuleName ] [-Version ] [-Prerelease] [-Tag ] +[[-DscResourceName] ] [-ModuleName ] [-Version ] [-Prerelease] [-Tag ] [-Repository ] [-Credential ] [-IncludeDependencies] [-WhatIf] [-Confirm] [] ``` @@ -77,6 +77,47 @@ PS C:\> Find-PSResource -Name "Microsoft.PowerShell.SecretManagement" -Version " This examples searches for the package with `-Name` "Microsoft.PowerShell.SecretManagement". It returns all versions which satisfy the specified `-Version` range by looking through the specified `-Repository` "PSGallery". At the time of writing this example those satisfying versions are: "0.9.1.0" and "1.0.0.0". +### Example 4 +```powershell +PS C:\> Find-PSResource -CommandName "Get-TargetResource" -Repository PSGallery + Name Version Prerelease ModuleName Repository + ---- ------- ---------- ---------- ---------- + Get-TargetResource 3.1.0.0 xPowerShellExecutionPolicy PSGallery + Get-TargetResource 1.0.0.4 WindowsDefender PSGallery + Get-TargetResource 1.2.0.0 SystemLocaleDsc PSGallery + Get-TargetResource 1.0.0.0 xInternetExplorerHomePage PSGallery + Get-TargetResource 4.0.1055.0 OctopusDSC PSGallery + Get-TargetResource 1.2.0.0 cRegFile PSGallery + Get-TargetResource 1.1.0.0 cWindowsErrorReporting PSGallery + Get-TargetResource 1.0.0.0 cVNIC PSGallery + Get-TargetResource 1.1.17.0 supVsts PSGallery + +``` + +This examples searches for all module resources with `-CommandName` "Get-TargetResource" from the `-Repository` PSGallery. It returns all the module resources which include a command named "Get-TargetResource" and also lists the following information for each module resource: version, name (displayed under ModuleName) and repository. To access the rest of the properties of the parent module resource, you can access the `$_.ParentResource` of the PSIncludedResourceInfo object returned from the CommandName parameter set. + +### Example 5 +```powershell +PS C:\> Find-PSResource -CommandName "Get-TargetResource" -ModuleName "SystemLocaleDsc" -Repository PSGallery + Name Version Prerelease ModuleName Repository + ---- ------- ---------- ---------- ---------- + Get-TargetResource 1.2.0.0 SystemLocaleDsc PSGallery +``` + +This examples searches for a module resource with a command named "Get-TargetResource" (via the `-CommandName` parameter), specifically from the module resource "SystemLocaleDsc" (via the `-ModuleName` parameter) from the `-Repository` PSGallery. The "SystemLocaleDsc" resource does indeed include a command named Get-TargetResource so this resource will be returned. The returned object lists the name of the command (displayed under Name) and the following information for the parent module resource: version, name (displayed under ModuleName) and repository. To access the rest of the properties of the parent module resource, you can access the `$_.ParentResource` of the PSIncludedResourceInfo object returned from the CommandName parameter set. + +### Example 6 +```powershell +PS C:\> Find-PSResource -DscResourceName "SystemLocale" -Repository PSGallery + Name Version Prerelease ModuleName Repository + ---- ------- ---------- ---------- ---------- + Get-TargetResource 8.5.0.0 ComputerManagementDsc PSGallery + Get-TargetResource 1.2.0.0 SystemLocaleDsc PSGallery + +``` + +This examples searches for all module resources with `-DscResourceName` "SystemLocale" from the `-Repository` PSGallery. It returns all the module resources which include a DSC resource named "SystemLocale" and also lists the following information for each module resource: version, name (displayed under ModuleName) and repository. To access the rest of the properties of the parent module resource, you can access the `$_.ParentResource` of the PSIncludedResourceInfo object returned from the DSCResourceName parameter set. + ## PARAMETERS ### -Credential diff --git a/src/PSGet.Format.ps1xml b/src/PSGet.Format.ps1xml index 80decb5de..695dc2813 100644 --- a/src/PSGet.Format.ps1xml +++ b/src/PSGet.Format.ps1xml @@ -25,5 +25,39 @@ + + PSIncludedResourceInfoTable + + Microsoft.PowerShell.PowerShellGet.UtilClasses.PSIncludedResourceInfo + + + + + + + + + + + + + + + + + + + + + Name + $_.ParentResource.Version + $_.ParentResource.PrereleaseLabel + $_.ParentResource.Name + $_.ParentResource.Repository + + + + + diff --git a/src/code/FindPSResource.cs b/src/code/FindPSResource.cs index 1e576ad29..f317c4280 100644 --- a/src/code/FindPSResource.cs +++ b/src/code/FindPSResource.cs @@ -21,7 +21,7 @@ namespace Microsoft.PowerShell.PowerShellGet.Cmdlets "PSResource", DefaultParameterSetName = ResourceNameParameterSet, SupportsShouldProcess = true)] - [OutputType(typeof(PSResourceInfo))] + [OutputType(typeof(PSResourceInfo), typeof(PSCommandResourceInfo))] public sealed class FindPSResource : PSCmdlet { #region Members @@ -76,7 +76,7 @@ public sealed class FindPSResource : PSCmdlet [Parameter(ParameterSetName = CommandNameParameterSet)] [Parameter(ParameterSetName = DscResourceNameParameterSet)] [ValidateNotNullOrEmpty] - public string ModuleName { get; set; } + public string[] ModuleName { get; set; } /// /// Specifies a list of command names that searched module packages will provide. Wildcards are supported. @@ -134,11 +134,11 @@ public sealed class FindPSResource : PSCmdlet protected override void BeginProcessing() { _source = new CancellationTokenSource(); - _cancellationToken = _source.Token; - - // Create a respository story (the PSResourceRepository.xml file) if it does not already exist - // This is to create a better experience for those who have just installed v3 and want to get up and running quickly - RepositorySettings.CheckRepositoryStore(); + _cancellationToken = _source.Token; + + // Create a respository story (the PSResourceRepository.xml file) if it does not already exist + // This is to create a better experience for those who have just installed v3 and want to get up and running quickly + RepositorySettings.CheckRepositoryStore(); } protected override void StopProcessing() @@ -155,19 +155,11 @@ protected override void ProcessRecord() break; case CommandNameParameterSet: - ThrowTerminatingError(new ErrorRecord( - new PSNotImplementedException("CommandNameParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), - "CommandParameterSetNotImplementedYet", - ErrorCategory.NotImplemented, - this)); + ProcessCommandOrDscParameterSet(isSearchingForCommands: true); break; case DscResourceNameParameterSet: - ThrowTerminatingError(new ErrorRecord( - new PSNotImplementedException("DscResourceNameParameterSet is not yet implemented. Please rerun cmdlet with other parameter set."), - "DscResourceParameterSetNotImplementedYet", - ErrorCategory.NotImplemented, - this)); + ProcessCommandOrDscParameterSet(isSearchingForCommands: false); break; default: @@ -256,6 +248,93 @@ private void ProcessResourceNameParameterSet() } } + private void ProcessCommandOrDscParameterSet(bool isSearchingForCommands) + { + var commandOrDSCNamesToSearch = Utils.ProcessNameWildcards( + pkgNames: isSearchingForCommands ? CommandName : DscResourceName, + errorMsgs: out string[] errorMsgs, + isContainWildcard: out bool nameContainsWildcard); + + if (nameContainsWildcard) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException("Wilcards are not supported for -CommandName or -DSCResourceName for Find-PSResource. So all CommandName or DSCResourceName entries will be discarded."), + "CommandDSCResourceNameWithWildcardsNotSupported", + ErrorCategory.InvalidArgument, + this)); + return; + } + + foreach (string error in errorMsgs) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException(error), + "ErrorFilteringCommandDscResourceNamesForUnsupportedWildcards", + ErrorCategory.InvalidArgument, + this)); + } + + // this catches the case where Name wasn't passed in as null or empty, + // but after filtering out unsupported wildcard names there are no elements left in commandOrDSCNamesToSearch + if (commandOrDSCNamesToSearch.Length == 0) + { + return; + } + + var moduleNamesToSearch = Utils.ProcessNameWildcards( + pkgNames: ModuleName, + errorMsgs: out string[] moduleErrorMsgs, + isContainWildcard: out bool _); + + foreach (string error in moduleErrorMsgs) + { + WriteError(new ErrorRecord( + new PSInvalidOperationException(error), + "ErrorFilteringModuleNamesForUnsupportedWildcards", + ErrorCategory.InvalidArgument, + this)); + } + + if (moduleNamesToSearch.Length == 0) + { + moduleNamesToSearch = new string[] {"*"}; + } + + FindHelper findHelper = new FindHelper(_cancellationToken, this); + List foundPackages = new List(); + + foreach (PSResourceInfo package in findHelper.FindByResourceName( + name: moduleNamesToSearch, + // provide type so Scripts endpoint for PSGallery won't be searched + type: isSearchingForCommands? ResourceType.Command : ResourceType.DscResource, + version: Version, + prerelease: Prerelease, + tag: Tag, + repository: Repository, + credential: Credential, + includeDependencies: IncludeDependencies)) + { + foundPackages.Add(package); + } + + // if a single package contains multiple commands we are interested in, return a unique entry for each: + // Command1 , PackageA + // Command2 , PackageA + foreach (string nameToSearch in commandOrDSCNamesToSearch) + { + foreach (var package in foundPackages) + { + // this check ensures DSC names provided as a Command name won't get returned mistakenly + // -CommandName "command1", "dsc1" <- (will not return or add DSC name) + if ((isSearchingForCommands && package.Includes.Command.Contains(nameToSearch)) || + (!isSearchingForCommands && package.Includes.DscResource.Contains(nameToSearch))) + { + WriteObject(new PSCommandResourceInfo(nameToSearch, package)); + } + } + } + } + #endregion } } diff --git a/src/code/PSResourceInfo.cs b/src/code/PSResourceInfo.cs index a70922618..5d681825c 100644 --- a/src/code/PSResourceInfo.cs +++ b/src/code/PSResourceInfo.cs @@ -199,6 +199,37 @@ public Dependency(string dependencyName, VersionRange dependencyVersionRange) #endregion + #region PSCommandResourceInfo + public sealed class PSCommandResourceInfo + { + // this object will represent a Command or DSCResource + // included by the PSResourceInfo property + + #region Properties + public string Name { get; } + + public PSResourceInfo ParentResource { get; } + #endregion + + #region Constructor + + /// + /// Constructor + /// + /// + /// Name of the command or DSC resource + /// the parent module resource the command or dsc resource belongs to + public PSCommandResourceInfo(string name, PSResourceInfo parentResource) + { + Name = name; + ParentResource = parentResource; + } + + #endregion + } + + #endregion + #region PSResourceInfo public sealed class PSResourceInfo @@ -485,7 +516,14 @@ public static bool TryConvert( } try - { + { + var typeInfo = ParseMetadataType(metadataToParse, repositoryName, type, out ArrayList commandNames, out ArrayList dscResourceNames); + var resourceHashtable = new Hashtable(); + resourceHashtable.Add(nameof(PSResourceInfo.Includes.Command), new PSObject(commandNames)); + resourceHashtable.Add(nameof(PSResourceInfo.Includes.DscResource), new PSObject(dscResourceNames)); + var includes = new ResourceIncludes(resourceHashtable); + + psGetInfo = new PSResourceInfo( additionalMetadata: null, author: ParseMetadataAuthor(metadataToParse), @@ -494,7 +532,7 @@ public static bool TryConvert( dependencies: ParseMetadataDependencies(metadataToParse), description: ParseMetadataDescription(metadataToParse), iconUri: ParseMetadataIconUri(metadataToParse), - includes: null, + includes: includes, installedDate: null, installedLocation: null, isPrelease: ParseMetadataIsPrerelease(metadataToParse), @@ -509,7 +547,8 @@ public static bool TryConvert( repository: repositoryName, repositorySourceLocation: null, tags: ParseMetadataTags(metadataToParse), - type: ParseMetadataType(metadataToParse, repositoryName, type), + // type: ParseMetadataType(metadataToParse, repositoryName, type), + type: typeInfo, updatedDate: null, version: ParseMetadataVersion(metadataToParse)); @@ -803,7 +842,11 @@ private static string[] ParseMetadataTags(IPackageSearchMetadata pkg) return pkg.Tags.Split(Delimeter, StringSplitOptions.RemoveEmptyEntries); } - private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, string repoName, ResourceType? pkgType) + private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, + string repoName, + ResourceType? pkgType, + out ArrayList commandNames, + out ArrayList dscResourceNames) { // possible type combinations: // M, C @@ -811,6 +854,8 @@ private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, string // M // S + commandNames = new ArrayList(); + dscResourceNames = new ArrayList(); string[] tags = ParseMetadataTags(pkg); ResourceType currentPkgType = ResourceType.Module; @@ -838,15 +883,18 @@ private static ResourceType ParseMetadataType(IPackageSearchMetadata pkg, string currentPkgType &= ~ResourceType.Module; currentPkgType |= ResourceType.Script; } - if (tag.StartsWith("PSCommand_")) + if (tag.StartsWith("PSCommand_", StringComparison.InvariantCultureIgnoreCase)) { currentPkgType |= ResourceType.Command; + commandNames.Add(tag.Split('_')[1]); } - if (String.Equals(tag, "PSIncludes_DscResource", StringComparison.InvariantCultureIgnoreCase)) + if (tag.StartsWith("PSDscResource_", StringComparison.InvariantCultureIgnoreCase)) { currentPkgType |= ResourceType.DscResource; + dscResourceNames.Add(tag.Split('_')[1]); } } + return currentPkgType; } diff --git a/test/FindPSResource.Tests.ps1 b/test/FindPSResource.Tests.ps1 index 71805f03c..57ab7a23b 100644 --- a/test/FindPSResource.Tests.ps1 +++ b/test/FindPSResource.Tests.ps1 @@ -9,6 +9,9 @@ Describe 'Test Find-PSResource for Module' { $TestGalleryName = Get-PoshTestGalleryName $PSGalleryName = Get-PSGalleryName $NuGetGalleryName = Get-NuGetGalleryName + $commandName = "Get-TargetResource" + $dscResourceName = "SystemLocale" + $parentModuleName = "SystemLocaleDsc" Get-NewPSResourceRepositoryFile Register-LocalRepos } @@ -239,4 +242,34 @@ Describe 'Test Find-PSResource for Module' { $resNonDefault = Find-PSResource -Name $moduleName -Repository $repoLowerPriorityRanking $resNonDefault.Repository | Should -Be $repoLowerPriorityRanking } + + It "find resource given CommandName (CommandNameParameterSet)" { + $res = Find-PSResource -CommandName $commandName -Repository $PSGalleryName + foreach ($item in $res) { + $item.Name | Should -Be $commandName + $item.ParentResource.Includes.Command | Should -Contain $commandName + } + } + + It "find resource given CommandName and ModuleName (CommandNameParameterSet)" { + $res = Find-PSResource -CommandName $commandName -ModuleName $parentModuleName -Repository $PSGalleryName + $res.Name | Should -Be $commandName + $res.ParentResource.Name | Should -Be $parentModuleName + $res.ParentResource.Includes.Command | Should -Contain $commandName + } + + It "find resource given DSCResourceName (DSCResourceNameParameterSet)" { + $res = Find-PSResource -DscResourceName $dscResourceName -Repository $PSGalleryName + foreach ($item in $res) { + $item.Name | Should -Be $dscResourceName + $item.ParentResource.Includes.DscResource | Should -Contain $dscResourceName + } + } + + It "find resource given DscResourceName and ModuleName (DSCResourceNameParameterSet)" { + $res = Find-PSResource -DscResourceName $dscResourceName -ModuleName $parentModuleName -Repository $PSGalleryName + $res.Name | Should -Be $dscResourceName + $res.ParentResource.Name | Should -Be $parentModuleName + $res.ParentResource.Includes.DscResource | Should -Contain $dscResourceName + } }