MyAlbum.psm1
# This is a sample PackageManagement provider. It is trying to discover photos in your remote file repository # and installs them to your local folder. # Import the localized Data Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename MyAlbum.Resource.psd1 #region Local variable definitions # Define the provider name $script:ProviderName = "MyAlbum" # The folder where stores the provider configuration file $script:LocalPath="$env:LOCALAPPDATA\Contoso\$script:ProviderName" $script:RegisteredPackageSources = $null $script:RegisteredPackageSourcesFilePath = Microsoft.PowerShell.Management\Join-Path -Path $script:LocalPath -ChildPath "MyAlbumPackageSource.xml" # Wildcard pattern matching configuration $script:wildcardOptions = [System.Management.Automation.WildcardOptions]::CultureInvariant -bor ` [System.Management.Automation.WildcardOptions]::IgnoreCase #endregion #region Provider APIs Implementation # Mandatory function for the PackageManagement providers. It returns the name of your provider. function Get-PackageProviderName { return $script:ProviderName } # Mandatory function for the PackageManagement providers. It initializes your provider before performing any actions. function Initialize-Provider { Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Initialize-Provider')) #add your intialize code here } <# Optional function that indicates what featues your provider supports. Returns a collection of features this provider supports. This is primarily for others to leveage your provider. Here is the existing features defined by PackageManagement: SupportsPowerShellModules = "supports-powershell-modules"; === find, install, powershell modules SupportsRegexSearch = "supports-regex-search"; === support Regualar expression search SupportsWildcardSearch = "supports-wildcard-search"; === support wildcard search SupportedExtensions = "file-extensions"; === package file extensions, e.g., .msi, .nupkg SupportedSchemes = "uri-schemes"; === url shemes, e.g., http, https, file MagicSignatures = "magic-signatures"; === bytes at the begining of a package file, .cab, .zip #> function Get-Feature { Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Get-Feature')) # Write out to the host. In this case, PackageManagement is the host. Write-Output -InputObject (New-Feature -name "file-extensions" -values @(".png")) <# Add more features that your provider supports here. e.g., Write-Output -InputObject (New-Feature -name "supports-python-modules") #> } # Optional function that returns dynamic parameters defined by the provider to the PackageManagement. function Get-DynamicOptions { param ( [Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] $category ) Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Get-DynamicOptions')) # There are available categories defined by PackageManagement: # Package - for searching for packages # Source - for package sources # Install - for Install/Uninstall/Get-InstalledPackage switch($category) { Package { Write-Output -InputObject (New-DynamicOption -Category $category -Name "Filter" -ExpectedType String -IsRequired $false) } Install { Write-Output -InputObject (New-DynamicOption -Category $category -Name "Destination" -ExpectedType String -IsRequired $true) } } } # Optional function that gets called when the user is registering a package source. # .e.g, Register-PackageSource -Name demo -Location C:\CameraRoll -ProviderName MyAlbum # If your provider supports Register-PackageSource, it is required to implement this function. function Add-PackageSource { [CmdletBinding()] param ( [string] $Name, [string] $Location, [bool] $Trusted ) Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Add-PackageSource')) Get-PSDrive if(-not (Microsoft.PowerShell.Management\Test-Path -path $Location)) { ThrowError -ExceptionName "System.ArgumentException" ` -ExceptionMessage ($LocalizedData.PathNotFound -f ($Location)) ` -ErrorId "PathNotFound" ` -CallerPSCmdlet $PSCmdlet ` -ErrorCategory InvalidArgument ` -ExceptionObject $Location return } # We do not allow "Register-PackageSource -Name a*" if(Test-WildcardPattern $Name) { ThrowError -ExceptionName "System.ArgumentException" ` -ExceptionMessage ($LocalizedData.PackageSourceNameContainsWildCards -f ($Name)) ` -ErrorId "PackageSourceNameContainsWildCards" ` -CallerPSCmdlet $PSCmdlet ` -ErrorCategory InvalidArgument ` -ExceptionObject $Name return } Set-PackageSourcesVariable -Force # Add new package source $packageSource = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{ Name = $Name SourceLocation = $Location.TrimEnd("\") Trusted=$Trusted Registered= $true InstallationPolicy = if($Trusted) {'Trusted'} else {'Untrusted'} }) $script:RegisteredPackageSources.Add($Name, $packageSource) Write-Verbose -Message ($LocalizedData.SourceRegistered -f ($Name, $Location)) # Persist the package sources Save-PackageSources # yield the package source to OneGet Write-Output -InputObject (New-PackageSourceAndYield -Source $packageSource) } # Optional function that unregisters a package Source. e.g., Unregister-PackageSource -Name album. # It is required to implement this function for providers that support Unregister-PackageSource. function Remove-PackageSource { param ( [string] $Name ) Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Remove-PackageSource')) Set-PackageSourcesVariable -Force # Check if $Name contains any wildcards if(Test-WildcardPattern $Name) { $message = $LocalizedData.PackageSourceNameContainsWildCards -f ($Name) Write-Error -Message $message -ErrorId "PackageSourceNameContainsWildCards" -Category InvalidOperation -TargetObject $Name return } # Error out if the specified source name is not in the registered package sources. if(-not $script:RegisteredPackageSources.Contains($Name)) { $message = $LocalizedData.PackageSourceNotFound -f ($Name) Write-Error -Message $message -ErrorId "PackageSourceNotFound" -Category InvalidOperation -TargetObject $Name return } # Remove the SourcesToBeRemoved $script:RegisteredPackageSources.Remove($Name) # Persist the package sources Save-PackageSources Write-Verbose ($LocalizedData.PackageSourceUnregistered -f ($Name)) } # This is an optional function that returns the registered package sources or the sources the provider can handle. # For exmaple, it gets called during find-package, install-package, get-packagesource etc. # PackageManagement uses this method to identify which provider can handle the packages from a particular source location. # Therefore in general this function needs to be implemented. function Resolve-PackageSource { Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Resolve-PackageSource')) # Use the $request object to get user's cmdline parameter values, or return the values back to PackageManagement. # Get the value of "-Source" from user's commandline input $SourceName = $request.PackageSources # get Sources from the registered config file Set-PackageSourcesVariable if(-not $SourceName) { $SourceName = "*" } foreach($src in $SourceName) { if($request.IsCanceled) { return } # Get the sources that registered before $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $src,$script:wildcardOptions $sourceFound = $false $script:RegisteredPackageSources.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object {$wildcardPattern.IsMatch($_.Key)} | Microsoft.PowerShell.Core\ForEach-Object { $source = $script:RegisteredPackageSources[$_.Key] $packageSource = New-PackageSourceAndYield -Source $source Write-Output -InputObject $packageSource $sourceFound = $true } # If a user does specify -Source but not registered if(-not $sourceFound) { # Get source name from the source location in case a user passes in location instead of name $sourceName = Get-SourceName -Location $src if($sourceName) { $source = $script:RegisteredPackageSources[$sourceName] $packageSource = New-PackageSourceAndYield -Source $source Write-Output -InputObject $packageSource } # So far we found the given source is not a registered package source Name nor Location # It depends on your provider's implementation whether you want to support unregistered source. # If you do, add your code here. In this example, we only suppport the registered ones. elseif( -not (Test-WildcardPattern $src)) { $message = $LocalizedData.PackageSourceNotFound -f ($src) Write-Error -Message $message -ErrorId "PackageSourceNotFound" -Category InvalidOperation -TargetObject $src } } } } # Optional function that finds packages by given name and version information. # It is required to implement this function for the providers that support find-package. For example, find-package -ProviderName MyAlbum -Source demo. function Find-Package { param( [string] $name, [string] $requiredVersion, [string] $minimumVersion, [string] $maximumVersion ) Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Find-Package')) # Read in the registered package source information to the memory Set-PackageSourcesVariable $ValidationResult = Validate-VersionParameters -Name $Name ` -MinimumVersion $MinimumVersion ` -MaximumVersion $MaximumVersion ` -RequiredVersion $RequiredVersion if(-not $ValidationResult) { # Return now as the version validation failed already return } # Get the cmdlet parameter values that were passed from the user via the $request.Options. # Here we can find out the package source name passed by a user. <# Commonly used properties of $request object: PackageSources -- -Source Options -- Get any user's input via Options Credential -- -Credential IsCanceled -- Is operation cancelling? #> $options = $request.Options foreach( $o in $options.Keys ) { Write-Debug ( "OPTION: {0} => {1}" -f ($o, $options[$o]) ) } # Check if a user specifies -Source $selectedSources = @() if($options -and $options.ContainsKey('Source')) { # Finding the matched package sources from the registered ones $sourceNames = $($options['Source']) Write-Verbose ($LocalizedData.SpecifiedSourceName -f ($sourceNames)) foreach($sourceName in $sourceNames) { if($script:RegisteredPackageSources.Contains($sourceName)) { # Found the matched registered source $selectedSources += $script:RegisteredPackageSources[$sourceName] } else { $sourceByLocation = Get-SourceName -Location $sourceName if ($sourceByLocation -ne $null) { $selectedSources += $script:RegisteredPackageSources[$sourceByLocation] } else { $message = $LocalizedData.PackageSourceNotFound -f ($sourceName) ThrowError -ExceptionName "System.ArgumentException" ` -ExceptionMessage $message ` -ErrorId "PackageSourceNotFound" ` -CallerPSCmdlet $PSCmdlet ` -ErrorCategory InvalidArgument ` -ExceptionObject $sourceName } } } } else { # User does not specify -Source, we will use the registered sources Write-Verbose $LocalizedData.NoSourceNameIsSpecified $script:RegisteredPackageSources.Values | Microsoft.PowerShell.Core\ForEach-Object { $selectedSources += $_ } } # finding the package foreach($source in $selectedSources) { if($request.IsCanceled) { return } $location = $source.SourceLocation if(-not (Test-Path $location)) { continue } # Find the photos $files = Get-ChildItem -Path $location -Filter '*.png' -Recurse | ` Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -like "*$name*") } foreach($file in $files) { <#add code here for handling filter #> if($request.IsCanceled) { return } # Note: FastPackageReference is used across multiple calls such as Find-package, Install-package, UnInstall-Package and Download-Package. # The format of FastPackageReference needs to be consistent within your provider. It usually contains package Name, Version and Source. # In the MyAlbum case, we choose the file full path just for demo purpose. $swidObject = @{ FastPackageReference = $file.FullName; Name = $file.Name; Version = New-Object System.Version ("0.1"); # Note: You need to fill in a proper package version. versionScheme = "MultiPartNumeric"; summary = "Add the summary of your package provider here"; Source = $location; } $sid = New-SoftwareIdentity @swidObject Write-Output -InputObject $sid } } } # Optional function that downloads a remote package file to a local location. It is called for Save-Package. # It is required to implement this function for the providers that support save-package. For example, save-package -Name Seattle -Path C:\ForSave\. function Download-Package { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $FastPackageReference, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Location ) Write-Debug ($LocalizedData.ProviderDebugMessage -f ('Download-Package')) Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) <# You need to add code here in your real provider: 1. parse the FastPackageReference for package name, version, source etc. 2. Find the matched source from the registered ones 3. Use the Source to download packages #> Install-PackageUtility -FastPackageReference $fastPackageReference -Location $Location -Request $request } # It is required to implement this function for the providers that support install-package. # for example, install-package -Name seattle -ProviderName myalbum -Source demo -Destination c:\myfolder function Install-Package { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $fastPackageReference ) Write-Debug -Message ($LocalizedData.ProviderDebugMessage -f ('Install-Package')) Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) $path = Get-Path -Request $request Install-PackageUtility -FastPackageReference $fastPackageReference -Location $path -Request $request } # A helper function for install-package and save-package function Install-PackageUtility { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $FastPackageReference, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Location, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Request ) # Check the source location if(-Not (Test-Path -Path $fastPackageReference)) { ThrowError -ExceptionName "System.ArgumentException" ` -ExceptionMessage ($LocalizedData.PathNotFound -f ($fastPackageReference)) ` -ErrorId "PathNotFound" ` -CallerPSCmdlet $CallerPSCmdlet ` -ErrorCategory InvalidArgument ` -ExceptionObject $fastPackageReference } # Check the destination location if(-Not (Test-Path -Path $Location)) { New-Item -Path $Location -ItemType Directory -Force } # Get the cmdlet parameter values that were passed from the user $force = $false $options = $request.Options if($options.ContainsKey('Force')) { $force = $options['Force'] } Copy-Item -Path $fastPackageReference -Destination $Location -Force:$force -WhatIf:$false -Confirm:$false $swidObject = @{ FastPackageReference = $fastPackageReference; Name = [System.IO.Path]::GetFileName($fastPackageReference); Version = New-Object System.Version ("0.1"); # Note: You need to fill in a proper package version versionScheme = "MultiPartNumeric"; summary = "Summary of your package provider"; Source = [System.IO.Path]::GetDirectoryName($fastPackageReference) } $swidTag = New-SoftwareIdentity @swidObject Write-Output -InputObject $swidTag } # It is required to implement this function for the providers that support UnInstall-Package. # For example, UnInstall-Package -Name seattle -ProviderName myalbum -Destination c:\myfolder. function UnInstall-Package { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $fastPackageReference ) Write-Debug -Message ($LocalizedData.ProviderDebugMessage -f ('Uninstall-Package')) Write-Debug -Message ($LocalizedData.FastPackageReference -f $fastPackageReference) $fileFullName = $fastPackageReference if(Test-Path -Path $fileFullName) { Remove-Item $fileFullName -Force -WhatIf:$false -Confirm:$false $swidObject = @{ FastPackageReference = $fileFullName; Name = [System.IO.Path]::GetFileName($fileFullName); Version = New-Object System.Version ("0.1"); # Note: You need to fill in a proper package version versionScheme = "MultiPartNumeric"; summary = "Summary of your package provider"; Source = [System.IO.Path]::GetDirectoryName($fileFullName) } $swidTag = New-SoftwareIdentity @swidObject Write-Output -InputObject $swidTag } } # Optional function that returns the packages that are installed. However it is required to implement this function for the providers # that support Get-Package. It's also called during install-package. # For example, Get-package -Destination c:\myfolder -ProviderName MyAlbum function Get-InstalledPackage { [CmdletBinding()] param ( [Parameter()] [string] $Name, [Parameter()] [string] $RequiredVersion, [Parameter()] [string] $MinimumVersion, [Parameter()] [string] $MaximumVersion ) Write-Debug -Message ($LocalizedData.ProviderDebugMessage -f ('Get-InstalledPackage')) #You can check the version here... #<your code> $fullPath = Get-Path -Request $request if (Test-Path -Path $fullPath) { # Find the photos $files = Get-ChildItem -Path $fullPath -Filter '*.png' -Recurse | ` Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -like "*$Name*") } foreach($file in $files) { if($request.IsCanceled) { return } $swidObject = @{ FastPackageReference = $file.FullName; Name = $file.Name; Version = New-Object System.Version ("0.1"); versionScheme = "MultiPartNumeric"; summary = "Summary of your package provider"; Source = $file.FullName; } $swidTag = New-SoftwareIdentity @swidObject Write-Output -InputObject $swidTag } } } #endregion #region Helper functions # Get the package destination path function Get-Path { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] $Request ) # Get the cmdlet parameter values that were passed from the user. Via Options, we can find out what's the installation path. $options = $Request.Options foreach( $o in $options.Keys ) { Write-Debug ( "OPTION: {0} => {1}" -f ($o, $options[$o]) ) } if($options -and $options.ContainsKey('Destination')) { $path = $($options['Destination']) return $path } } # Test if the $Name contains any wildcard characters function Test-WildcardPattern { [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory=$true)] [ValidateNotNull()] $Name ) return [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name) } # Find package source name from a given location function Get-SourceName { [CmdletBinding()] [OutputType("string")] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $Location ) Set-PackageSourcesVariable foreach($source in $script:RegisteredPackageSources.Values) { if($source.SourceLocation -eq $Location) { return $source.Name } } } # Yield the package source to OneGet function New-PackageSourceAndYield { param ( [Parameter(Mandatory)] $Source ) # create a new package source $src = New-PackageSource -Name $Source.Name ` -Location $Source.SourceLocation ` -Trusted $Source.Trusted ` -Registered $Source.Registered ` Write-Verbose ( $LocalizedData.PackageSourceDetails -f ($src.Name, $src.Location, $src.IsTrusted, $src.IsRegistered) ) # return the package source object. Write-Output -InputObject $src } # Read the registered package sources from its configuration file function Set-PackageSourcesVariable { param([switch]$Force) if(-not $script:RegisteredPackageSources -or $Force) { if(Microsoft.PowerShell.Management\Test-Path $script:RegisteredPackageSourcesFilePath) { $script:RegisteredPackageSources = DeSerializePSObject -Path $script:RegisteredPackageSourcesFilePath } else { $script:RegisteredPackageSources = [ordered]@{} } } } # Read xml content to into an object function DeSerializePSObject { [CmdletBinding(PositionalBinding=$false)] Param ( [Parameter(Mandatory=$true)] $Path ) # You can use import-clixml here. However this cmdlet is not available on Nano Server yet, so we choose PSSerializer to # make the provider run on both full server and Nano Server. $filecontent = Microsoft.PowerShell.Management\Get-Content -Path $Path [System.Management.Automation.PSSerializer]::Deserialize($filecontent) } # Save the package source to the configuration file function Save-PackageSources { if($script:RegisteredPackageSources) { if(-not (Microsoft.PowerShell.Management\Test-Path $script:LocalPath)) { $null = Microsoft.PowerShell.Management\New-Item -Path $script:LocalPath ` -ItemType Directory ` -Force ` -ErrorAction SilentlyContinue ` -WarningAction SilentlyContinue ` -Confirm:$false -WhatIf:$false } # You can use export-clixml here. However this cmdlet is not available on Nano Server yet, so we choose PSSerializer to # make the provider run on both full server and Nano Server. Microsoft.PowerShell.Utility\Out-File ` -FilePath $script:RegisteredPackageSourcesFilePath ` -Force ` -InputObject ([System.Management.Automation.PSSerializer]::Serialize($script:RegisteredPackageSources)) } } # Validate versions function Validate-VersionParameters { Param( [Parameter()] [String[]] $Name, [Parameter()] [string] $MinimumVersion, [Parameter()] [string] $RequiredVersion, [Parameter()] [string] $MaximumVersion, [Parameter()] [Switch] $AllVersions ) <# Add code here to validate versions #> # Once we complete the validation, return the result. As a sample here, we assume we have passed the version checking return $true } # Utility to throw an errorrecord function ThrowError { param ( [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCmdlet] $CallerPSCmdlet, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ExceptionName, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ExceptionMessage, [System.Object] $ExceptionObject, [parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ErrorId, [parameter(Mandatory = $true)] [ValidateNotNull()] [System.Management.Automation.ErrorCategory] $ErrorCategory ) $exception = New-Object $ExceptionName $ExceptionMessage; $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject $CallerPSCmdlet.ThrowTerminatingError($errorRecord) } #endregion |