ProGetAutomation.psm1
Add-Type -AssemblyName 'System.Net.Http' Add-Type -AssemblyName 'System.Web' Add-Type -AssemblyName 'System.IO.Compression.FileSystem' $functionsDirPath = Join-Path -Path $PSScriptRoot -ChildPath 'Functions' if( (Test-Path -Path $functionsDirPath -PathType Container) ) { foreach( $item in Get-ChildItem -Path $functionsDirPath -Filter '*.ps1' ) { Write-Debug -Message $item.FullName . $item.FullName } } function Add-ProGetUniversalPackageFile { <# .SYNOPSIS Adds files and directories to a ProGet universal package. .DESCRIPTION The `Add-ProGetUniversalPackageFile` function adds files and directories to a upack file (use `New-ProGetUniversalPackage` to create a upack file). All items are added under the `package` directory in the package, per the upack specification. Files are added to the package using their names. They are always added to the `package` directory in the package. For example, if you added `C:\Projects\Zip\Zip\Zip.psd1` to the package, it would get added at `package\Zip.psd1`. Directories are added into a directory in the `package` directory. The directory in the package will use the name of the source directory. For example, if you add 'C:\Projects\Zip', all items will be added to the package at `package\Zip`. You can change the name an item will have in the package with the `PackageItemName` parameter. Path separators are allowed, so you can put any item into any directory in the package. If you don't want to add an entire directory to the package, but instead want a filtered set of files from that directory, pipe the filtered list of files to `Add-ProGetUniversalPackageFile` and use the `BasePath` parameter to specify the base path of the incoming files. `Add-ProGetUniversalPackageFile` removes the base path from each file and uses the remaining path as the file's name in the package. If you want to change an item's parent directory structure in the package, pass the parent path you want to the `PackageParentPath` parameter. For example, if you passed `tools` as the `PackageParentPath`, every item added will be put in a `package\tools` directory in the package. You can control the compression level of items getting added with the `CompressionLevel` parameter. The default is `Optimal`. Other options are `Fastest` (larger files, compresses faster) and `None`. This function uses the `Zip` PowerShell module, which uses the native .NET `System.IO.Compression` namespace/classes to do its work. .EXAMPLE Get-ChildItem 'C:\Projects\Zip' | Add-ProGetUniversalPackageFile -PackagePath 'zip.upack' Demonstrates how to pipe the files you want to add to your package into `Add-ProGetUniversalPackageFile`. In this case, all the files and directories in the `C:\Projects\Zip` directory are added to the package to the `package` directory. .EXAMPLE Get-ChildItem -Path 'C:\Projects\Zip' -Filter '*.ps1' -Recurse | Add-ProGetUniversalPackageFile -PackagePath 'zip.upack' -BasePath 'C:\Projects\Zip' This is like the previous example, but instead of adding every file under `C:\Projects\Zip`, we're only adding files with a `.ps1` extension. Since we're piping all the files to the `Add-ProGetUniversalPackageFile` function, we need to pass the base path of our search to the `BasePath` parameter. Otherwise, every file would get added to the `package` directory without preserving their directory structure. Instead, the `BasePath` is removed from every file's path and the remaining path is used as the item's path in the package. .EXAMPLE Get-Item -Path '.\Zip' | Add-ProGetUniversalPackageFile -PackagePath 'zip.upack' -PackageParentPath 'tools' Demonstrates how to customize the directory in the package files will be added at. In this case, the `.\Zip` directory will be put in a `package\tools` directory, e.g. `package\tools\Zip`. .EXAMPLE Get-ChildItem 'C:\Projects\Zip' | Add-ProGetUniversalPackageFile -PackagePath 'zip.upack' -EntryName 'tools\ZipModule' Demonstrates how to change the name of an item. In this case, the `C:\Projects\Zip` directory will be added to the package with a path of `package\tools\ZipModule` instead of `package\Zip`. #> [CmdletBinding(DefaultParameterSetName='ItemName')] param( [Parameter(Mandatory)] [string] # The path to the upack file. The files will be added to this package. $PackagePath, [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('FullName')] [Alias('Path')] [string[]] # The files/directories to add to the upack file. Normally, you would pipe file/directory objects to `Add-ProGetUniversalPackageFile`. You may also pass any object that has a `FullName` or `Path property. You may also pass the path as a string. # # If you pass a directory object or path to a directory, that directory and all its sub-directories will be added to the upack file. # # All files/directories are added to the `packages` directory in the upack file. $InputObject, [Parameter(ParameterSetName='BasePath')] [string] # When determining a file's path/name in the package, the value of this parameter is removed from the beginning of each file's path. Use this parameter if you are piping in a filtered list of files from a directory instead of the directory itself. $BasePath, [Parameter(ParameterSetName='ItemName')] [ValidatePattern('^[^\\/]')] [ValidatePattern('[^\\/]$')] [string] # By default, items are added to the package using their name. You can change the name with this parameter. For example, if you added file `Zip.psd1` and passed `NewZip.psd1` as the value to the parameter, the file would get added to the package as `NewZip.psd1`. $PackageItemName, [ValidatePattern('^[^\\/]')] [string] $PackageParentPath, [IO.Compression.CompressionLevel] # The compression level of the upack file. The default is `Optimal`. Pass `Fastest` to compress faster but have a larger file. Pass `None` to not compress at all. $CompressionLevel = [IO.Compression.CompressionLevel]::Optimal, [Switch] # By default, if a file already exists in the upack file, you'll get an error. Use this switch to replace any existing files. $Force, [switch] # Suppress progress messages while adding files to the package. $Quiet ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Write-Debug -Message ('ProGetAutomation\Add-ProGetUniversalPackageFile BEGIN') $parentPath = 'package' if( $PackageParentPath ) { $parentPath = Join-Path -Path $parentPath -ChildPath $PackageParentPath } $params = @{ } if( $BasePath ) { $params['BasePath'] = $BasePath } if( $PackageItemName ) { $params['EntryName'] = $PackageItemName } if( $Force ) { $params['Force'] = $true } if( $Quiet ) { $params['Quiet'] = $true } $items = New-Object 'Collections.Generic.List[string]' } process { foreach( $item in $InputObject ) { $items.Add($item) } } end { $items | Add-ZipArchiveEntry -ZipArchivePath $PackagePath -EntryParentPath $parentPath -CompressionLevel $CompressionLevel @params Write-Debug -Message ('ProGetAutomation\Add-ProGetUniversalPackageFile END') } } # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Add-PSTypeName { [CmdletBinding()] param( [Parameter(Mandatory=$true,ValueFromPipeline=$true)] $InputObject, [Parameter(Mandatory=$true,ParameterSetName='PackageInfo')] [Switch] $PackageInfo, [Parameter(Mandatory=$true,ParameterSetName='Native.Feed')] [Switch] $NativeFeed ) process { Set-StrictMode -Version 'Latest' $typeName = 'Inedo.ProGet.{0}' -f $PSCmdlet.ParameterSetName $InputObject.pstypenames.Add( $typeName ) if( $PackageInfo ) { if( -not ($InputObject | Get-Member -Name 'group') ) { $InputObject | Add-Member -MemberType NoteProperty -Name 'group' -Value '' } } $InputObject } } function Get-ProGetAsset { <# .SYNOPSIS Gets metadata about items in an asset directory. .DESCRIPTION The `Get-ProGetAsset` function gets metadata from ProGet about assets. Pass the name of the root asset directory to the `DirectoryName` parameter. Information about all the files in that asset directory is returned. If the URL to an asset directory in ProGet is `https://proget.example.com/assets/versions/subdirectory/file`, the directory parameter is the first directory after `assets/` in this example `versions`, The path parameter the rest of the url in this case `subdirectory/file`. If you also pass a value to the `$filter` parameter, only files that match `$filter` value in the directory will be returned. Wildcards are supported. Pass a ProGet session object to the `$Session` parameter. This object controls what instance of ProGet to use and what credentials and/or API keys to use. Use the `New-ProGetSession` function to create session objects. .Example Get-ProGetAsset -Session $session -Path 'myAsset' -DirectoryName 'versions' Demonstrates how to get metadata about an asset. In this case, information about the `/versions/myAsset` file is returned. if `myAsset` is a directory then all files in that directory will be returned .Example Get-ProGetAsset -Session $session -Directory 'versions/subdirectory' Demonstrates how to get metadata from all files in the `versions/subdirectory` asset directory. If no files found an empty list is returned. #> param( [Parameter(Mandatory = $true)] [Object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory = $true)] [string] # The name of a valid path to the directory to get metadata of the desired assets in ProGet. $DirectoryName, [string] # The path to the subdirectory in the asset directory in ProGet. $Path, [string] # Name of the asset in the ProGet assets directory that will be retrieved. only file metadata that match `$Name` in the directory will be returned. Wildcards are supported. $Filter ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $uri = '/endpoints/{0}/dir/{1}' -f $DirectoryName,$Path if(!$Filter) { $Filter = '*' } return Invoke-ProGetRestMethod -Session $Session -Path $uri -Method Get | Where-Object { $_.Name -like $Filter } } function Get-ProGetAssetContent { <# .SYNOPSIS Gets the content of an asset in an asset directory. .DESCRIPTION The `Get-ProGetAssetContent` function gets an asset's content from ProGet. Pass the name of the root asset directory to the `DirectoryName` parameter. Pass the path to the asset to the `Path` parameter. If the URL to an asset directory in ProGet is `https://proget.example.com/assets/versions/subdirectory/file`, the directory parameter is the first directory after `assets/` (in this example `versions`). The `Path` parameter would be the rest of the url in this case `subdirectory/file`. If an asset doesn't exist, an error will be written and nothing is returned. Pass a ProGet session object to the `Session` parameter. This object controls what instance of ProGet to use and what credentials and/or API keys to use. Use the `New-ProGetSession` function to create session objects. .Example Get-ProGetAssetContent -Session $session -DirectoryName 'versions' -Path 'subdirectory/file.json' Demonstrates how to get the contents of an asset. In this case, the `subdirectory/file.json` asset's contents in the `versions` asset directory is returned. #> param( [Parameter(Mandatory = $true)] [Object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory = $true)] [string] # The name of the asset's asset directory. $DirectoryName, [string] # The path to the file in the asset directory, without the asset directory's name. $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $uri = '/endpoints/{0}/content/{1}' -f $DirectoryName,$Path return Invoke-ProGetRestMethod -Session $Session -Path $uri -Method Get -Raw } function Get-ProGetFeed { <# .SYNOPSIS Gets the feeds in a ProGet instance. .DESCRIPTION The `Get-ProGetFeed` function gets all the feeds from a ProGet instance. Pass the session to the ProGet instance to the `Session` parameter. Use `New-ProGetSession` to create a session. By default, only active feeds are returned. Use the `-Force` switch to also return inactive feeds. To get a specific feed, pass its name to the `Name` parameter. If the feed by that name doesn't exist, nothing is returned and no errors are written. This function uses the `Feeds_GetFeed` and `Feeds_GetFeeds` endpoints in ProGet's [native API](https://inedo.com/support/documentation/proget/reference/api/native). .EXAMPLE Get-ProGetFeed -Session $session Demonstrates how to get all the feeds in a ProGet instance. .EXAMPLE Get-ProGetFeed -Session $session -Name PowerShell Demonstrates how to get a specific feed. In this case, the `PowerShell` feed is returned. #> [CmdletBinding(DefaultParameterSetName='AllFeeds')] param( [Parameter(Mandatory)] [object] $Session, [Parameter(Mandatory,ParameterSetName='ByName')] [string] # By default, all feeds are returned. Use this parameter to return a specific feed using its name. $Name, [Parameter(Mandatory,ParameterSetName='ByID')] [string] # By default, all feeds are returned. Use this parameter to return a specific feed using its ID. $ID, [Parameter(ParameterSetName='AllFeeds')] [Switch] # By default, only active feeds are returned. Use this witch to return inactive feeds, too. $Force ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $parameter = @{ 'IncludeInactive_Indicator' = $Force.IsPresent; } $methodName = 'Feeds_GetFeeds' if( $Name ) { $methodName = 'Feeds_GetFeed' $parameter = @{ 'Feed_Name' = $Name; } } elseif( $ID ) { $methodName = 'Feeds_GetFeed' $parameter = @{ 'Feed_Id' = $ID; } } Invoke-ProGetNativeApiMethod -Session $Session -Name $methodName -Parameter $parameter | Where-Object { $_ } | Add-PSTypeName -NativeFeed } function Get-ProGetRequestHeader { [CmdletBinding()] param( [Parameter(Mandatory)] [Object] $Session ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $headers = @{} if( $Session.ApiKey ) { $headers['X-ApiKey'] = $Session.ApiKey; } if( $Session.Credential ) { $bytes = [Text.Encoding]::UTF8.GetBytes(('{0}:{1}' -f $Session.Credential.UserName,$Session.Credential.GetNetworkCredential().Password)) $creds = 'Basic ' + [Convert]::ToBase64String($bytes) $headers['Authorization'] = $creds } return $headers } function Get-ProGetUniversalPackage { <# .SYNOPSIS Gets ProGet universal package information. .DESCRIPTION The `Get-ProGetUniversalPackage` function gets all the packages in a ProGet universal feed. Pass a ProGet sesion to the `Session` parameter (use `New-ProGetSession` to create a session). Pass the name of the universal feed to the `FeedName` parameter. You can get information about a specific package by passing its name to the `Name` parameter. Wildcards are supported. If the package is in a group, you must pass the group's name to the `GroupName` parameter. Otherwise, ProGet won't find it (i.e. if you don't pass the group name, ProGet only looks for a package not in a group). If the package doesn't exist, you'll get an error. To get all the packages in a group, pass the group name to the `GroupName` parameter and nothing to the `Name` parameter. (Note: there is currently a bug in ProGet 4.8.6 where this functionality doesn't work.) You can use wildcards to search for packages with names or in groups. Whenever you do a wildcard search, the function downloads *all* packages from ProGet and searches through them locally. If a wildcard search finds no packages, nothing happens (i.e. you won't see any errors). The ProGet API doesn't return a `group` property on objecs that aren't in a group. This function adds a `group` property whose value is an empty string. This function uses ProGet's [universal feed API](https://inedo.com/support/documentation/upack/feed-api/endpoints). .EXAMPLE Get-ProGetUniversalPackage -Session $session -FeedName 'Apps' Demonstrates how to get a list of all packages in the `Apps` feed. .EXAMPLE Get-ProGetUniversalPackage -Session $session -FeedName 'Apps' -Name 'ProGetAutomation' Demonstrates how to get a specific package from ProGet that is not in a group. In this case, the `ProGetAutomation` package will be returned. If a package doesn't exist, nothing is returned. .EXAMPLE Get-ProGetUniversalPackage -Session $session -FeedName 'Apps' -GroupName 'PSModules' -Name 'ProGetAutomation' Demonstrates how to get a specific package in a specific group in a universal feed. In this case, will return the `ProGetAutomation` package in the `PSModules` group in the `Apps` feed. .EXAMPLE Get-ProGetUniversalPackage -Session $session -FeedName 'Apps' -Name 'ProGet*' Demonstrates how to get multiple packages using wildcards. In this case, any package that begins with `ProGet` would be returned. .EXAMPLE Get-ProGetUniversalPackage -Session $session -FeedName 'Apps' -GroupName 'PSModules' Demonstrates how to get a list of all packages in a specific group in a universal feed. In this case, all packages in the `PSModules` group in the `Apps` feed will be returned. Note: due to a bug in ProGet 4.8.6, no packages will be returned. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object] # A session object representing the ProGet instance to connect to. Use `New-ProGetSession` to create a new session. $Session, [Parameter(Mandatory)] [string] # The name of the feed whose packages to get. $FeedName, [string] # The name of a specific package to get. Wildcards supported. If the package is in a group, you must pass its group name to the `GroupName` parameter $Name, [string] $GroupName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $searchingName = ($Name -and [WildcardPattern]::ContainsWildcardCharacters($Name)) $searchingGroup = ($GroupName -and [WildcardPattern]::ContainsWildcardCharacters($GroupName)) $queryString = '' if( -not $searchingName -and -not $searchingGroup ) { $queryString = & { if( $Name ) { 'name={0}' -f [uri]::EscapeDataString($Name) } if( $GroupName ) { 'group={0}' -f [Uri]::EscapeDataString($GroupName) } } } if( $queryString ) { $queryString = '?{0}' -f ($queryString -join '&') } Invoke-ProGetRestMethod -Session $Session -Path ('/upack/{0}/packages{1}' -f [uri]::EscapeDataString($FeedName),$queryString) -Method Get | Where-Object { if( -not $searchingName ) { return $true } return $_.name -like $Name } | Add-PSTypeName -PackageInfo | Where-Object { if( -not $GroupName -or -not $searchingGroup ) { return $true } return $_.group -like $GroupName } } function Invoke-ProGetNativeApiMethod { <# .SYNOPSIS Calls a method on ProGet's Native API. .DESCRIPTION The `Invoke-ProGetNativeApiMethod` calls a method on ProGet's Native API. From Inedo: > This API endpoint should be avoided if there is an alternate API endpoint available, as those are much easier to use and will likely not change. In other words, use a native API at your own peril. .EXAMPLE Invoke-ProGetNativeApiMethod -Session $session -Name 'Feeds_CreateOrUpdateProGetFeed' -Parameter @{ Feed_Name = 'Apps' } Demonstrates how to call `Invoke-ProGetNativeApiMethod`. In this example, it is calling the `Feeds_CreateOrUpdateProGetFeed` method to create a new Universal feed named `Apps`. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory)] [string] # The name of the API method to use. The list can be found at `http://inedo.com/support/documentation/proget/reference/api/native` or in your ProGet installation at `/reference/api/native` $Name, [hashtable] $Parameter ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if( -not $Parameter ) { $Parameter = @{} } Invoke-ProGetRestMethod -Session $Session -Path ('/api/json/{0}' -f $Name) -Method Post -Parameter $Parameter -ContentType Json } function Invoke-ProGetRestMethod { <# .SYNOPSIS Invokes a ProGet REST method. .DESCRIPTION The `Invoke-ProGetRestMethod` invokes a ProGet REST API method. You pass the path to the endpoint (everything after `/api/`) via the `Name` parameter, the HTTP method to use via the `Method` parameter, and the parameters to pass in the body of the request via the `Parameter` parameter. This function converts the `Parameter` hashtable to JSON and sends it in the body of the request. You also need to pass an object that represents the ProGet instance and API key to use when connecting via the `Session` parameter. Use the `New-ProGetSession` function to create a session object. #> [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='None')] param( [Parameter(Mandatory)] [object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory)] [String] # The path to the API endpoint. $Path, [Microsoft.PowerShell.Commands.WebRequestMethod] # The HTTP/web method to use. The default is `POST`. $Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Post, [Parameter(ParameterSetName='ByParameter')] [hashtable] # The parameters to pass to the method. $Parameter, [Parameter(ParameterSetName='ByParameter')] [string] [ValidateSet('Form','Json')] # Controls how the parameters are sent to the API. The default is `Form`, which sends them as URL-encoded name/value pairs (i.e. like a HTML form submission). The other options is `Json`, which converts the parameters to JSON and sends that JSON text as the content/body of the request. This parameter is ignored if there are no parmaeters to send or if the `InFile` parameter is used. $ContentType, [Parameter(ParameterSetName='ByFile')] [String] # Send the contents of the file at this path as the body of the web request. $InFile, [Parameter(ParameterSetName='ByContent')] [String] # Send the content of this string as the body of the web request. $Body, [Switch] # Return the raw content from the request instead of attempting to convert the response from JSON into an object. $Raw ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $uri = New-Object 'Uri' -ArgumentList $Session.Url,$Path $requestContentType = 'application/json; charset=utf-8' $debugBody = $null if( $PSCmdlet.ParameterSetName -eq 'ByParameter' ) { if( $ContentType -eq 'Json' ) { $Body = $Parameter | ConvertTo-Json -Depth 100 $debugBody = $Body -replace '("API_Key": +")[^"]+','$1********' } else { $Body = $Parameter.Keys | ForEach-Object { '{0}={1}' -f [Web.HttpUtility]::UrlEncode($_),[Web.HttpUtility]::UrlEncode($Parameter[$_]) } $Body = $Body -join '&' $requestContentType = 'application/x-www-form-urlencoded; charset=utf-8' $debugBody = $Parameter.Keys | ForEach-Object { $value = $Parameter[$_] if( $_ -eq 'API_Key' ) { $value = '********' } ' {0}={1}' -f $_,$value } } } $headers = Get-ProGetRequestHeader -Session $Session #$DebugPreference = 'Continue' Write-Debug -Message ('{0} {1}' -f $Method.ToString().ToUpperInvariant(),($uri -replace '\b(API_Key=)([^&]+)','$1********')) Write-Debug -Message (' Content-Type: {0}' -f $requestContentType) foreach( $headerName in $headers.Keys ) { $value = $headers[$headerName] if( @( 'X-ApiKey', 'Authorization' ) -contains $headerName ) { $value = '*' * 8 } Write-Debug -Message (' {0}: {1}' -f $headerName,$value) } if( $debugBody ) { $debugBody | Write-Verbose } $errorsAtStart = $Global:Error.Count try { $optionalParams = @{ 'ContentType' = $requestContentType; } if( $PSCmdlet.ParameterSetName -in ('ByParameter', 'ByContent') ) { if( $Body ) { $optionalParams['Body'] = $Body } else { $optionalParams.Remove('ContentType') } } elseif( $PSCmdlet.ParameterSetName -eq ('ByFile') ) { $optionalParams['Infile'] = $Infile $requestContentType = 'multipart/form-data' } if( $Session.Credential ) { $optionalParams['Credential'] = $Session.Credential } $cmdName = 'Invoke-RestMethod' if( $Raw ) { $cmdName = 'Invoke-WebRequest' } if( (Get-Command -Name $cmdName -ParameterName 'UseBasicParsing' -ErrorAction Ignore) ) { $optionalParams['UseBasicParsing'] = $true } if (Get-Command -Name $cmdName -ParameterName 'AllowUnencryptedAuthentication' -ErrorAction Ignore) { $optionalParams['AllowUnencryptedAuthentication'] = $true } if( $Method -eq [Microsoft.PowerShell.Commands.WebRequestMethod]::Get -or $PSCmdlet.ShouldProcess($uri,$Method) ) { & $cmdName -Method $Method -Uri $uri @optionalParams -Headers $headers | ForEach-Object { $_ } } } catch [Net.WebException] { for( $idx = $errorsAtStart; $idx -lt $Global:Error.Count; ++$idx ) { $Global:Error.RemoveAt(0) } Write-Error -ErrorRecord $_ -ErrorAction $ErrorActionPreference } } function New-ProGetFeed { <# .SYNOPSIS Creates a new ProGet package feed .DESCRIPTION The `New-ProGetFeed` function creates a new ProGet feed. Use the `Type` parameter to specify the feed type (valid values are 'VSIX', 'RubyGems', 'Docker', 'ProGet', 'Maven', 'Bower', 'npm', 'Deployment', 'Chocolatey', 'NuGet', 'PowerShell'). The `Session` parameter controls the instance of ProGet to connect to. This function uses ProGet's Native API, so an API key is required. Use `New-ProGetSession` to create a session with your API key. .EXAMPLE New-ProGetFeed -Session $ProGetSession -Name 'Apps' -Type 'ProGet' Demonstrates how to call `New-ProGetFeed`. In this case, a new Universal package feed named 'Apps' will be created for the specified ProGet Uri #> [CmdletBinding()] param( # The session includes ProGet's URI and the API key. Use `New-ProGetSession` to create session objects [Parameter(Mandatory)] [pscustomobject] $Session, # The feed name indicates the name of the package feed that will be created. [Parameter(Mandatory)] [Alias('FeedName')] [string] $Name, # The feed type indicates the type of package feed to create. # Valid feed types are ('VSIX', 'RubyGems', 'Docker', 'ProGet', 'Maven', 'Bower', 'npm', 'Deployment', 'Chocolatey', 'NuGet', 'PowerShell') - check here for a latest list - https://inedo.com/support/documentation/proget/feed-types/universal [Parameter(Mandatory)] [Alias('FeedType')] [string] $Type ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if( -not $Session.ApiKey ) { Write-Error -Message ('We are unable to create new package feed ''{0}/{1}'' because your ProGet session is missing an API key. This function uses ProGet''s Native API, which requires an API key. Use `New-ProGetSession` to create a session object that uses an API key.' -f $Type, $Name) return } if( $Type -eq 'ProGet' ) { $msg = 'ProGet renamed its "ProGet" feed type name to "Universal". Please update the value of ' + 'New-ProGetFeed''s "Type" parameter from "ProGet" to "Universal".' Write-Warning $msg $Type = 'Universal' } $Parameters = @{ 'FeedType_Name' = $Type; 'Feed_Name' = $Name; } $feedExists = Test-ProGetFeed -Session $Session -Name $Name -Type $Type if( $feedExists ) { Write-Error -Message ('Unable to create {0} {1} feed: a feed with that name and type already exists.' -f $Type, $Name) -ErrorAction $ErrorActionPreference return } Write-Verbose -Message ('Creating {0} {1} feed in ProGet instance "{2}".' -f $Type, $Name, $Session.Url) $null = Invoke-ProGetNativeApiMethod -Session $Session -Name 'Feeds_CreateFeed' -Parameter $Parameters } function New-ProGetSession { <# .SYNOPSIS Creates a session object used to connect with a ProGet instance. .DESCRIPTION The `New-ProGetSession` function creates and returns a session object that is required when calling any function in the ProGetAutomation module that communicates with ProGet. The session includes ProGet's URL and the credentials and/or API key to use when making requests. .EXAMPLE $session = New-ProGetSession -Url 'https://proget.com' -Credential $credential Demonstrates how to call `New-ProGetSession`. In this case, the returned session object can be passed to other ProGetAutomation module functions to communicate with ProGet at `https://proget.com` with the credential in `$credential`. #> [CmdletBinding()] param( # The URL to the ProGet instance to use. [Parameter(Mandatory)] [Alias('Uri')] [uri] $Url, # The credential to use when making requests to ProGet utilizing the Universal Feed API. [pscredential] $Credential, # The API key to use when making requests to ProGet utilizing the Native API [string] $ApiKey ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState return [pscustomobject]@{ Url = $Url; Credential = $Credential; ApiKey = $ApiKey } | Add-Member -MemberType AliasProperty -Name 'Uri' -Value 'Url' -PassThru } function New-ProGetUniversalPackage { <# .SYNOPSIS Creates a ProGet universal package file. .DESCRIPTION The `New-ProGetUniversalPackage` function creates a ProGet universal package file. The file will only contain a upack.json file. Pass the path to the file to create to the `OutFile` parameter (the file must not exist or you'll get an error). You must supply a name (with the `Name` parameter) and a version (with the `Version` parameter). Names can only contain letters, numbers, periods, underscores, and hyphens. Version must be a valid semantic version. Pass `New-ProGetUniversalPackage` has the following parameters that add the appropriate metadata to the package's upack.json manifest: * GroupName * Title * ProjectUri * IconUri * Description * Tag (creates the `tags` property) * Dependency (creates the `dependencies` property) * Reason (creates the `createdReason` property) * Author (creates the `createdBy` property) You can pass additional custom metadata to the `AdditionalMetadata` property. It is recommended that all custom metadata be prefixed with an underscore to prevent collision with future standard metadata. The `New-ProGetUniversalPackage` function always adds two additional pieces of metadata: * `createdDate`, the UTC date/time this function gets called * `createdUsing`, a string that identifies the ProGetAutomation module as the tool used; it includes the module's version, the PowerShell version, and, if available, the PowerShell edition. A `IO.FileInfo` object is returned for the just-created package. The `Zip` PowerShell module is used to create the ZIP archive and add the upack.json file to it. By default, optimal compression is used. You can customize your compression level with the `CompressionLevel` parameter. See the [upack.json Manifest Specification page](https://inedo.com/support/documentation/upack/universal-packages/metacontent-guidance/manifest-specification) for more information about the format and contents of the upack.json file. Once you've created the package, you can then add additional files to it with the `Add-ProGetUniversalPackage` function. .EXAMPLE New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' Demonstrates how to create a minimal upack package. .EXAMPLE New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -GroupName 'WHS/PowerShell' -Title 'ProGet Automation' -ProjectUri 'https://github.com/webmd-health-services/ProGetAutomation' -IconUri 'https://github.com/webmd-health-services/ProGetAutomation/icon.png' -Description 'A PowerShell module for automationg ProGet.' -Tag @( 'powershell', 'module', 'inedo', 'proget' ) -Dependency @( 'zip' ) -Reason 'Because the world needs more PowerShell!' -Author 'WebMD Health Services' Demonstrates how to create a upack package with all required and optional metadata. (The ProGetAutomation package doesn't have any dependencies. The example shows one for illustrative purposes only.) .EXAMPLE New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -AdditionalMetadata @{ '_whs' = @{ 'fubar' = 'snafu' } } Demonstrates how to add custom metadata to your package. .EXAMPLE New-ProGetUniversalPackage -OutFile 'package.upack' -Version '0.0.0' -Name 'ProGetAutomation' -CompressionLevel Fastest Demonstrates how to change the compression level of the package. #> [CmdletBinding()] [OutputType([IO.FileInfo])] param( [Parameter(Mandatory)] [string] # Path to the package. The package is created here. The filename should have a .upack extension. $OutFile, [Parameter(Mandatory)] [string] # The version of the package. Semantic Version 2 supported. $Version, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9._-]+$')] [string] # The name of the package. Must only contain letters, numbers, periods, underscores or hyphens. $Name, [ValidatePattern('(^[A-Za-z0-9._/-]+$)|(^$)')] [ValidatePattern('(^[^/])|(^$)')] [ValidatePattern('([^/]$)|(^$)')] [string] # The group name of the package. Must only contain letters, numbers, periods, underscores, forward slashes, or hyphens. Must not begin or end with forward slashes. $GroupName, [ValidateLength(1,50)] [string] # The package's title/display name. Any characters are allowed. Can't be longer than 50 characters. $Title, [uri] # The URI to the project. $ProjectUri, [uri] # The URI to the projet/package's icon. The icon may be in the package itself. If it is, pass `package://path/to/icon`. $IconUri, [string] # A full description of the package. Formatted as Markdown in the ProGet UI. $Description, [string[]] [ValidatePattern('^[A-Za-z0-9._-]+$')] # An array of tags. Each tag must only contain letters, numbers, periods, underscores, and hyphens. $Tag, [string[]] # A list of dependencies as package names. Must be formatted like: # # * �group�/�package-name� # * �group�/�package-name�:�version� # * �group�/�package-name�:�version�:�sha-hash� $Dependency, [string] # The reason the package is getting created. $Reason, [string] # The author of the package. $Author, [hashtable] # Any additional metadata for the package. It is recommended that you prefix custom metadata with an underscore to prevent possible collisions with future system metadata. # # If you provide a parameter and duplicate that parameter's metadata in this hashtable, the parameter value takes precedence. # # $AdditionalMetadata = @{ }, [IO.Compression.CompressionLevel] # The compression level to use. The default is `Optimal`. Other values are `Fastest` (larger file, created faster) or `None` (nothing is compressed). $CompressionLevel = [IO.Compression.CompressionLevel]::Optimal ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $tempDir = Join-Path -Path ([IO.Path]::GetTempPath()) -ChildPath ('{0}.{1}' -f ($OutFile | Split-Path -Leaf),([IO.Path]::GetRandomFileName())) New-Item -Path $tempDir -ItemType 'Directory' | Out-Null try { $upackJsonPath = Join-Path -Path $tempDir -ChildPath 'upack.json' [hashtable]$upackJson = $AdditionalMetadata.Clone() $upackJson['name'] = $Name $upackJson['version'] = $Version if( -not $upackJson.ContainsKey('createdUsing') ) { $psEdition = '' if( $PSVersionTable.ContainsKey('PSEdition') ) { $psEdition = '; {0}' -f $PSVersionTable['PSEdition'] } $upackJson['createdUsing'] = 'ProGetAutomation/{0} (PowerShell {1}{2})' -f (Get-Module -Name 'ProGetAutomation').Version,$PSVersionTable['PSVersion'],$psEdition } if( -not $upackJson.ContainsKey('createdDate') ) { $upackJson['createdDate'] = (Get-Date).ToUniversalTime().ToString('O') } $parameterToMetadataMap = @{ 'GroupName' = 'group'; 'Title' = 'title'; 'ProjectUri' = 'projectUri'; 'IconUri' = 'iconUri'; 'Description' = 'description'; 'Tag' = 'tags'; 'Dependency' = 'dependencies'; 'Reason' = 'createdReason'; 'Author' = 'createdBy'; } foreach( $parameterName in $parameterToMetadataMap.Keys ) { if( -not $PSBoundParameters[$parameterName] ) { continue } $metadataName = $parameterToMetadataMap[$parameterName] $upackJson[$metadataName] = $PSBoundParameters[$parameterName] } $upackJson | ForEach-Object { [pscustomobject]$_ } | ConvertTo-Json -Depth 50 | Set-Content -Path $upackJsonPath $archive = New-ZipArchive -Path $OutFile -CompressionLevel $CompressionLevel $upackJsonPath | Add-ZipArchiveEntry -ZipArchivePath $archive.FullName -CompressionLevel $CompressionLevel $archive } finally { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction Ignore } } function Publish-ProGetUniversalPackage { <# .SYNOPSIS Publishes a package to the specified ProGet instance .DESCRIPTION The `Publish-ProGetUniversalPackage` function will upload a package to the `FeedName` universal feed. It uses .NET 4.5's `HttpClient` to upload the file. .EXAMPLE Publish-ProGetUniversalPackage -Session $ProGetSession -FeedName 'Apps' -PackagePath 'C:\ProGetPackages\TestPackage.upack' Demonstrates how to call `Publish-ProGetUniversalPackage`. In this case, the package named 'TestPackage.upack' will be published to the 'Apps' feed located at $Session.Url using the $Session.Credential authentication credentials #> [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] [pscustomobject] # The session includes ProGet's URI and the credentials to use when utilizing ProGet's API. $Session, [Parameter(Mandatory=$true)] [string] # The feed name indicates the appropriate feed where the package should be published. $FeedName, [Parameter(Mandatory=$true)] [string] # The path to the package that will be published to ProGet. $PackagePath, [int] # The timeout (in seconds) for the upload. The default is 100 seconds. $Timeout = 100, [Switch] # Replace the package if it already exists in ProGet. $Force ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $shouldProcessCaption = ('creating {0} package' -f $PackagePath) $proGetPackageUri = New-Object 'Uri' $Session.Url,('/upack/{0}' -f $FeedName) $proGetCredential = $Session.Credential $PackagePath = Resolve-Path -Path $PackagePath | Select-Object -ExpandProperty 'ProviderPath' if( -not $PackagePath ) { Write-Error -Message ('Package ''{0}'' does not exist.' -f $PSBoundParameters['PackagePath']) return } $userMsg = '' if( $proGetCredential ) { $userMsg = ' as ''{0}''' -f $proGetCredential.UserName } if( -not $Force ) { $version = $null $name = $null $group = $null $zip = $null $foundUpackJson = $true $invalidUpackJson = $false try { $zip = [IO.Compression.ZipFile]::OpenRead($PackagePath) $foundUpackJson = $false foreach( $entry in $zip.Entries ) { if($entry.FullName -ne "upack.json" ) { continue } $foundUpackJson = $true $stream = $entry.Open() $stringReader = New-Object 'IO.StreamReader' $stream try { $packageJson = $stringReader.ReadToEnd() | ConvertFrom-Json $version = $packageJson.version $name = $packageJson.name if( $packageJson | Get-Member -Name 'group' ) { $group = $packageJson.group } } catch { $invalidUpackJson = $true } finally { $stringReader.Close() $stream.Close() } break } } catch { Write-Error -Message ('The upack file ''{0}'' isn''t a valid ZIP file.' -f $PackagePath) return } finally { if( $zip ) { $zip.Dispose() } } if( -not $foundUpackJson ) { Write-Error -Message ('The upack file ''{0}'' is invalid. It must contain a upack.json metadata file. See http://inedo.com/support/documentation/various/universal-packages/universal-feed-api for more information.' -f $PackagePath) return } if( $invalidUpackJson ) { Write-Error -Message (@" The upack.json metadata file in '$($PackagePath)' is invalid. It must be a valid JSON file with ''version'' and ''name'' properties that have values, e.g. { ""name"": ""HDARS"", ""version": ""1.3.9"" } See http://inedo.com/support/documentation/various/universal-packages/universal-feed-api for more information. "@) return } if( -not $name -or -not $version ) { [string[]]$propertyNames = @( 'name', 'version') | Where-Object { -not (Get-Variable -Name $_ -ValueOnly) } $description = 'property doesn''t have a value' if( $propertyNames.Count -gt 1 ) { $description = 'properties don''t have values' } $emptyPropertyNames = $propertyNames -join ''' and ''' Write-Error -Message ('The upack.json metadata file in ''{0}'' is invalid. The ''{1}'' {2}. See http://inedo.com/support/documentation/various/universal-packages/universal-feed-api for more information.' -f $PackagePath,$emptyPropertyNames,$description) return } $packageInfo = Get-ProGetUniversalPackage -Session $Session -FeedName $FeedName -GroupName $group -Name $name -ErrorAction Ignore if( $packageInfo -and $packageInfo.versions -contains $version ) { Write-Error -Message ('Package {0} {1} already exists in universal ProGet feed ''{2}''.' -f $name,$version,$proGetPackageUri) return } } $operationDescription = 'Uploading ''{0}'' package to ProGet at ''{1}''{2}.' -f ($PackagePath | Split-Path -Leaf), $proGetPackageUri, $userMsg if( $PSCmdlet.ShouldProcess($operationDescription, $operationDescription, $shouldProcessCaption) ) { Write-Verbose -Message $operationDescription $networkCred = $null if( $proGetCredential ) { $networkCred = $proGetCredential.GetNetworkCredential() } $maxDuration = New-Object 'TimeSpan' 0,0,$Timeout [Net.Http.HttpClientHandler]$httpClientHandler = $null [Net.Http.HttpClient]$httpClient = $null [IO.FileStream]$packageStream = $null [Net.Http.StreamContent]$streamContent = $null [Threading.Tasks.Task[Net.Http.HttpResponseMessage]]$httpResponseMessage = $null [Net.Http.HttpResponseMessage]$response = $null [Threading.CancellationTokenSource]$canceller = $null try { $httpClientHandler = New-Object 'Net.Http.HttpClientHandler' if( $proGetCredential ) { $httpClientHandler.UseDefaultCredentials = $false $httpClientHandler.Credentials = $networkCred } $httpClientHandler.PreAuthenticate = $true; $httpClient = New-Object 'Net.Http.HttpClient' ([Net.Http.HttpMessageHandler]$httpClientHandler) $httpClient.Timeout = $maxDuration $packageStream = New-Object 'IO.FileStream' ($PackagePath, 'Open', 'Read') $streamContent = New-Object 'Net.Http.StreamContent' ([IO.Stream]$packageStream) $streamContent.Headers.ContentType = New-Object 'Net.Http.Headers.MediaTypeHeaderValue' ('application/octet-stream') $canceller = New-Object 'Threading.CancellationTokenSource' $httpResponseMessage = $httpClient.PutAsync($proGetPackageUri, [Net.Http.HttpContent]$streamContent, $canceller.Token) if( -not $httpResponseMessage.Wait($maxDuration) ) { $canceller.Cancel() $maxTries = 1000 $tryNum = 0 while( $tryNum -lt $maxTries -and -not $httpResponseMessage.IsCanceled ) { $tryNum += 1 Start-Sleep -Milliseconds 100 } Write-Error -Message ('Uploading file ''{0}'' to ''{1}'' timed out after {2} second(s). To increase this timeout, set the Timeout parameter to the number of seconds to wait for the upload to complete.' -f $PackagePath,$proGetPackageUri,$Timeout) return } $response = $httpResponseMessage.Result if( -not $response.IsSuccessStatusCode ) { Write-Error -Message ('Failed to upload ''{0}'' to ''{1}''. We received the following ''{2} {3}'' response:{4} {4}{5}{4} {4}' -f $PackagePath,$proGetPackageUri,[int]$response.StatusCode,$response.StatusCode,[Environment]::NewLine,$response.Content.ReadAsStringAsync().Result) return } } catch { $ex = $_.Exception while( $ex.InnerException ) { $ex = $ex.InnerException } if( $ex -is [Threading.Tasks.TaskCanceledException] ) { Write-Error -Message ('Uploading file ''{0}'' to ''{1}'' was cancelled. This is usually because the upload took longer than the timeout, which was {2} second(s). Use the Timeout parameter to increase the upload timeout.' -f $PackagePath,$proGetPackageUri,$Timeout) return } Write-Error -Message ('An unknown error occurred uploading ''{0}'' to ''{1}'': {2}' -f $PackagePath,$proGetPackageUri,$_) return } finally { $disposables = @( 'httpClientHandler', 'httpClient', 'canceller', 'packageStream', 'streamContent', 'httpResponseMessage', 'response' ) $disposables | ForEach-Object { Get-Variable -Name $_ -ValueOnly -ErrorAction Ignore } | Where-Object { $_ -ne $null } | ForEach-Object { $_.Dispose() } $disposables | ForEach-Object { Remove-Variable -Name $_ -Force -ErrorAction Ignore } } } } function Read-ProGetUniversalPackageFile { <# .SYNOPSIS Reads the contents of a file from a package in a ProGet universal feed. .DESCRIPTION The `Read-ProGetUniversalPackageFile` reads the contents of a file from a package in a ProGet universal feed. Use this function to read parts of a universal package directly from ProGet without downloading the entire package. Pass the name of the universal feed to the `FeedName` parameter. Pass the name of the package to the `Name` parameter. Pass the path of the file in the package to the `Path` parameter. The path should include the `package` part of the path. ProGet is sensitive to directory separator characters. Make sure you use the same kind as the tool that created your package. By default, the file is read from the latest/most recent version of the package. To read a file from a specific version, pass that version to the `Version` parameter. This function uses the [Download File Package endpoint](https://inedo.com/support/documentation/upack/feed-api/endpoints#download-package-file) of ProGet's universal API. .EXAMPLE Read-ProGetUniversalPackageFile -Session $session -FeedName 'Apps' -Name 'MyApp' -Path 'upack.json' Demonstrates how to read the upack.json file from a package in a ProGet universal feed without downloading the entire package. In this example, the upack.json file is read from the package. .EXAMPLE Read-ProGetUniversalPackageFile -Session $session -FeedName 'Apps' -Name 'MyApp' -Path 'package/readme.md' Demonstrates how to read the readme.md file that was included in a package. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory=$true)] [string] # The name of the feed where the package can be found. $FeedName, [Parameter(Mandatory=$true)] [string] $Name, [string] # The package version to check. Defaults to the latest, most recent package. $Version, [Parameter(Mandatory=$true)] [string] # The relative path to the file in the package. ProGet is sensitive to directory separator characters. Make sure to use the same kind as the tool that created your package. ProGet sees "package\file" and "package/file" differently. $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $uriPath = '/upack/{0}/download-file/{1}/' -f ($FeedName,$Name | ForEach-Object { [Uri]::EscapeUriString($_) }) if( $Version ) { $uriPath = '{0}{1}/?' -f $uriPath,[Uri]::EscapeUriString($Version) } else { $uriPath = '{0}?latest&' -f $uriPath } $uriPath = '{0}path={1}' -f $uriPath,[Uri]::EscapeUriString($Path) Invoke-ProGetRestMethod -Session $Session -Path $uriPath -Parameter @{ } -Method Get -Raw } function Remove-ProGetAsset { <# .SYNOPSIS Removes assets from ProGet. .DESCRIPTION The `Remove-ProGetAsset` function removes assets from ProGet. Pass the name of the root asset directory to the `DirectoryName` parameter. If the URL to an asset directory in ProGet is `https://proget.example.com/assets/versions/subdirectory/file`, the directory parameter is the first directory after `assets/` in this example `versions`, The path parameter is the rest of the url, in this case `subdirectory/file`. If the file does not exist no error will be thrown. All the files in the asset directory that match `$filter` parameter will be deleted. .EXAMPLE Remove-ProGetAsset -Session $session -Path 'myAssetName' -DirectoryName 'versions' Removes asset or assets that match `myAssetName`. if `myAssetName` is a directory it will delete the files in the directory but not the directory itself. .Example Remove-ProGetAsset -Session $session -Path 'versions/myAssetName' -DirectoryName 'example' Removes asset or assets that match `example/versions/myAssetName` in ProGet .Example Remove-ProGetAsset -Session $session -Path 'versions/example' -DirectoryName 'subexample' -filter '*a*' Removes all assets that match the wildcard `subexample/versions/example/*a*` #> param( [Parameter(Mandatory = $true)] [Object] # A session object that represents the ProGet instance to use. Use the `New-ProGetSession` function to create session objects. $Session, [Parameter(Mandatory = $true)] [string] # The name of the root asset directory to Remove the desired asset in ProGet. $DirectoryName, [string] # the asset path in the ProGet assets directory that will be removed. If the file does not exist no error will be thrown. $Path, [string] # Name of the assets in the ProGet assets directory that will be deleted. only files that match `$filter` in the directory will be deleted. Wildcards are supported. $Filter ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $uri = '/endpoints/{0}/content/{1}' -f $DirectoryName,$Path $assetList = Get-ProGetAsset -Session $Session -Path $Path -DirectoryName $DirectoryName -Filter $filter foreach($asset in $assetList) { $asset = $asset.Name if($Path) { $asset = (join-Path -Path $Path -ChildPath $asset) } $uri = '/endpoints/{0}/content/{1}' -f $DirectoryName, $asset Invoke-ProGetRestMethod -Session $Session -Path $uri -Method Delete } } function Remove-ProGetFeed { <# .SYNOPSIS Removes a feed from ProGet. .DESCRIPTION The `Remove-ProGetFeed` function removes a feed from ProGet. All packages in the feed are also deleted. Pass the session to the ProGet instance from which to delete the feed to the `Session` parameter (use the `New-ProGetSession` function to create a session. Pass the ID of the feed to the `ID` parameter. You can also pipe feed IDs or feed objects returned by `Get-ProGetFeed`. Since this has the potential to be a disastrous operation (did we mention all the packages in the feed will also get deleted) and can't be undone, you'll be asked to confirm the deletion. If you don't want to be prompted, use the `-Force` switch. This is dangerous. This function uses the `Feeds_DeleteFeed` endpoint in [ProGet's native API](https://inedo.com/support/documentation/proget/reference/api/native). .EXAMPLE Remove-ProGetFeed -Session $session -ID 4398 Demonstrates how to delete a feed by passing its ID to the `ID` parameter. .EXAMPLE $feed | Remove-ProGetFeed -Session $session Demonstrates that you can pipe feed objects to `Remove-ProGetFeed` to remove those feeds. Use `Get-ProGetFeed` to get a feed objects. .EXAMPLE 4398 | Remove-ProGetFeed -Session $session Demonstrates that you can pipe feed IDs to `Remove-ProGetFeed` to remove those feeds. #> [CmdletBinding(SupportsShouldProcess,ConfirmImpact='High')] param( [Parameter(Mandatory)] [object] # The session to the ProGet instance to use. $Session, [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias('Feed_Id')] [int] # The ID of the feed to remove. You may pipe feed IDs as integers or feed objects returned by the `Get-ProGetFeed` function. $ID, [Switch] # Force the deletion of the feed without prompting for confirmation. This is dangerous. Deleting a feed deletes all its packages. $Force ) process { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $feed = Get-ProGetFeed -Session $Session -ID $ID if( -not $feed ) { return } $parameter = @{ Feed_Id = $ID } $description = 'Are you sure you want to delete {0} feed "{1}" (ID: {2}) and all its packages? THIS ACTION CANNOT BE UNDONE.' -f $feed.FeedType_Name,$feed.Feed_Name,$ID if( $Force -or $PSCmdlet.ShouldProcess($description,$description,('Confirm Deletion of {0} Feed "{1}"' -f $feed.FeedType_Name,$feed.Feed_Name)) ) { Invoke-ProGetNativeApiMethod -Session $Session -Name 'Feeds_DeleteFeed' -Parameter $parameter } } } function Remove-ProGetUniversalPackage { <# .SYNOPSIS Removes a package from a ProGet universal feed. .DESCRIPTION The `Remove-ProGetUniversalPackage` function removes a package from a ProGet universal feed. Pass the session to the ProGet instance from which the package should get deleted to the `Session` parmeter (use `New-ProGetSession` to create a session). Pass the feed from which the package should get deleted to the `FeedName` parameter. Pass the name of the package to the `Name` parameter. Pass the package version to delete to the `Version` parameter. If the package is in a group, pass the group name to the `GroupName` parameter. If the package doesn't exist, you'll get an error. This function uses ProGet's [universal feed API](https://inedo.com/support/documentation/upack/feed-api/endpoints). .EXAMPLE Remove-ProGetUniversalPackage -Session $session -FeeName 'PowerShell' -Name 'ProGetAutomation' -Version '0.7.0' Demonstrates how to delete a specific package version. In this case, package `ProGetAutomation` version `0.7.0` is deleted from the `PowerShell` feed. .EXAMPLE Remove-ProGetUniversalPackage -Session $session -FeeName 'PowerShell' -Name 'ProGetAutomation' -Version '0.7.0' -GroupName 'Modules' Demonstrates how to delete a specific package version when a package is in a group. In this case, package `ProGetAutomation` version `0.7.0` in the `Modules` group is deleted from the `PowerShell` feed. #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [object] # A session to the ProGet instance from which the package should get deleted. Use `New-ProGetSession` to create a session. $Session, [Parameter(Mandatory)] [string] # The name of the feed from which the package should be deleted. $FeedName, [Parameter(Mandatory)] [string] # The name of the package to delete. $Name, [Parameter(Mandatory)] [string] # The specific package version to delete. $Version, [string] # The package's group. $GroupName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $groupStem = '' if( $GroupName ) { $groupStem = '{0}/' -f [uri]::EscapeDataString($GroupName) } $path = '/upack/{0}/delete/{1}{2}/{3}' -f [uri]::EscapeDataString($FeedName),$groupStem,[uri]::EscapeDataString($Name),[uri]::EscapeDataString($Version) Invoke-ProGetRestMethod -Session $Session -Path $path -Method Delete } function Send-ProGetAsset { <# .SYNOPSIS Uploads file to a ProGet asset directory. ***This is an internal ProGetAutomation function. Use `Set-ProGetAsset` instead.*** .DESCRIPTION The `Send-ProGetAsset` function is an internal ProGetAutomation function. You should be using `Set-ProGetAsset` instead. The `Send-ProGetAsset` function uploads a file to a ProGet asset directory. The file can be any size. ProGet has undocumented capabilities to upload files in parts instead of the whole file and this function uses those undocumented features. Pass the session to ProGet to the `Session` parameter. Pass the path to the asset to the `AssetPath` parameter, e.g. `/endpoints/DIRECTORY_NAME/content/PATH_TO_FILE`. Pass the path to the source file to upload to the `FilePath` parameter. Pass the size, in bytes, of each part to the `MaxRequestSize` parameter. The default max request content size in IIS is 30 MB/28.6 MiB and in Apache is 1 GB. This function is adapted from https://gist.github.com/inedo-builds/cbee07725b3e227b0b566d028d4d3d07. .EXAMPLE Send-ProGetAsset -Session $Session -AssetPath '/endpoints/Installer/content/PowerShell/PowerShell-7.3.3.exe` -FilePath ~\Downloads\PowerShell-7.3.3.exe -MaxRequestSize 5mb Demonstrates how to use `Send-ProGetAsset`. In this example, the "~\Downloads\PowerShell-7.3.3.exe" file will be uploaded to the "Installers" asset directory at "Powershell/PowerShell-7.3.3.exe" #> [CmdletBinding()] param( # The session to ProGet. Use `New-ProGetSession` to create a session object. [Parameter(Mandatory)] [Object] $Session, # The asset's path. This is the path to the asset upload endpoint and is usually # `/endpoints/DIR_NAME/content/ASSET_PATH`. [Parameter(Mandatory)] [String] $AssetPath, # Path to the local file to upload. [Parameter(Mandatory)] [String] $FilePath, # The size of each part/request to upload to ProGet. [Parameter(Mandatory)] [UInt32] $MaxRequestSize ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState $headers = Get-ProGetRequestHeader -Session $Session $id = (New-Guid).ToString('N') function New-ProGetMultipartUploadRequest { [CmdletBinding()] param( [Parameter(Mandatory)] [Uri] $BaseUrl, [Parameter(Mandatory, ParameterSetName='Part')] [UInt32] $Index, [Parameter(Mandatory, ParameterSetName='Part')] [UInt64] $Offset, [Parameter(Mandatory, ParameterSetName='Part')] [UInt64] $TotalSize, [Parameter(Mandatory, ParameterSetName='Part')] [UInt32] $PartSize, [Parameter(Mandatory, ParameterSetName='Part')] [UInt32] $TotalParts, [Parameter(Mandatory, ParameterSetName='Complete')] [switch] $Complete ) $reqUrl = "$($BaseUrl)?id=$($id)" $contentLength = 0 if ($Complete) { $reqUrl = "$($reqUrl)&multipart=complete" } else { $reqUrl = "$($reqUrl)&multipart=upload" + "&index=$($Index)" + "&offset=$($Offset)" + "&totalSize=$($TotalSize)" + "&partSize=$($PartSize)" + "&totalParts=$($TotalParts)" $contentLength = $PartSize } $req = [System.Net.WebRequest]::CreateHttp($reqUrl) foreach ($headerName in $headers.Keys) { $req.Headers[$headerName] = $headers[$headerName] } $req.Method = 'POST' $req.ContentLength = $contentLength $req.AllowWriteStreamBuffering = $false Write-Verbose "POST $($reqUrl)" foreach ($headerName in $req.Headers.AllKeys) { $headerValue = $req.Headers[$headerName] if ($headerName -eq 'Authorization') { $headerValue = "Basic $('*' * ($headerValue.Length - 7))" } Write-Debug " $($headerName): $($headerValue)" } Write-Debug '' return $req } # Adapted from https://gist.github.com/inedo-builds/cbee07725b3e227b0b566d028d4d3d07 $FilePath = Resolve-Path -Path $FilePath | Select-Object -ExpandProperty 'ProviderPath' if (-not $FilePath) { return } $fileInfo = Get-Item -Path $FilePath if ($fileInfo.Length -eq 0) { return } $baseUrl = "$($Session.Url.ToString().TrimEnd('/'))/$($AssetPath.TrimStart('/'))" $activity = "Uploading ""$($FilePath | Resolve-Path -Relative)"" to $($baseUrl)." $remainder = [UInt64]0 [UInt64]$totalBytesRead = 0 $lastWriteProgress = [Diagnostics.Stopwatch]::StartNew() $writeProgressEvery = [TimeSpan]::New(0, 0, 0, 0, 100) $fileStream = [IO.FileStream]::New($FilePath, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::Read, 4096, [IO.FileOptions]::SequentialScan) try { $fileLength = $fileStream.Length $totalParts = [Math]::DivRem([long]$fileLength, [long]$MaxRequestSize, [ref]$remainder) if ($remainder -ne 0) { $totalParts++ } for($index = 0 ; $index -lt $totalParts ; $index++) { $offset = $index * $MaxRequestSize $chunkSize = $MaxRequestSize if($index -eq ($totalParts - 1)) { $chunkSize = $fileLength - $offset } $req = New-ProGetMultipartUploadRequest -BaseUrl $baseUrl ` -Index $index ` -Offset $offset ` -TotalSize $fileLength ` -PartSize $chunkSize ` -TotalParts $totalParts Write-Debug "[chunk $($index + 1) of $($totalParts); bytes $($offset) - $($offset + $chunkSize)]" $reqStream = $req.GetRequestStream() try { $buffer = [Array]::CreateInstance([System.Byte], 32767) if ($index -eq 0 -or $lastWriteProgress.Elapsed -gt $writeProgressEvery) { $percentComplete = $totalBytesRead / $fileLength * 100 $status = "Uploading chunk $($index + 1) of $($totalParts)" Write-Progress -Activity $activity -Status $status -PercentComplete $percentComplete $lastWriteProgress.Restart() } $totalChunkBytesRead = 0 while ($true) { $bytesRead = $fileStream.Read($buffer, 0, [Math]::Min($chunkSize - $totalChunkBytesRead, $buffer.Length)) if($bytesRead -eq 0) { break } $reqStream.Write($buffer, 0, $bytesRead) $totalBytesRead += $bytesRead $totalChunkBytesRead += $bytesRead if($totalChunkBytesRead -ge $chunkSize) { break } } } finally { if ($null -ne $reqStream) { $reqStream.Dispose() } } $response = $null try { $response = $req.GetResponse() } finally { if($null -ne $response) { $response.Dispose() } } } Write-Progress -Activity $activity -Status "Completing upload." -PercentComplete 100 $req = New-ProGetMultipartUploadRequest -BaseUrl $baseUrl -Complete $response = $null try { $response = $req.GetResponse() } finally { if ($null -ne $response) { $response.Dispose() } } } finally { Write-Progress -Activity $activity -Completed if ($null -ne $fileStream) { $fileStream.Dispose() } } } function Set-ProGetAsset { <# .SYNOPSIS Adds and updates assets to the ProGet asset manager. .DESCRIPTION The `Set-ProGetAsset` adds assets to a ProGet asset directory. Pass the name of the asset directory to the `DirectoryName` parameter. Pass the path to the asset in the asset directory to the `Path` parameter. Pass the path to the local file to upload to the `FilePath` parameter or the content (as a string) to the `Content` parameter. .EXAMPLE Set-ProGetAsset -Session $session -DirectoryName 'assetDirectory' -Path 'subdir/exampleAsset.txt' -FilePath 'path/to/file.txt' Example of publishing local file `path/to/file.txt` to ProGet in the `assetDirectory/subdir` folder as `exampleAsset.txt`. .EXAMPLE Set-ProGetAsset -Session $session -Directory 'assetDirectory' -Path 'exampleAsset.txt' -Content $bodyContent Example of publishing content contained in the $bodyContent variable to ProGet in the `assetDirectory` folder. #> [CmdletBinding()] param( # A session object to the ProGet instance to use. Use the `New-ProGetSession` function to create # a session object. [Parameter(Mandatory)] [Object] $Session, # The name of an existing asset directory. The function will escape any URL-sensitive characters. [Parameter(Mandatory)] [String] $DirectoryName, # The path where the asset will be published. Any directories that do not exist will be created automatically. # This is treated as a URL path, so you must escape any URL-sensitive characters. [Parameter(Mandatory)] [String] $Path, # The relative path of a file to be published as an asset. [Parameter(Mandatory, ParameterSetName='ByFile')] [String] $FilePath, # The maximum size in bytes of the request's content to send to ProGet. The default is # 30 megabytes/28.6 mebibytes (the default maximum request content size in IIS). # # If a file is greater than this size, `Set-ProGetAsset` will upload the file in `MaxRequestSize` chunks, each # chunk sent in a separate HTTP request. Set this to the maximum allowed content size of your web server. For # IIS, this is configured in the system.webServer/security/requestFiltering/requestFiltering/requestLimits # element's `maxAllowedContentLength` attribute. In Apache, this is configured with the `LimitRequestBody` # directive. [int] $MaxRequestSize = 30000000, # The content to be published as an asset. [Parameter(Mandatory, ParameterSetName='ByContent')] [String] $Content ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if ($FilePath) { if (-not (Test-Path -Path $FilePath)) { $msg = "Could not upload file ""$($FilePath)"" to ProGet asset directory ""$($DirectoryName)"" because " + 'that file does not exist.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } # Create a zero-byte file, otherwise ProGet responds with a 404 not found when uploading the file. $Content = '' } $assetPath = "/endpoints/$([Uri]::EscapeDataString($DirectoryName))/content/$($Path.TrimStart('/'))" if (-not (Test-ProGetFeed -Session $Session -Name $DirectoryName -Type Asset)) { $msg = "Failed to upload file ""$($FilePath)"" to ProGet asset directory ""$($DirectoryName)"" because that " + 'asset directory does not exist.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } Invoke-ProGetRestMethod -Session $Session -Path $assetPath -Method Post -Body $Content if ($FilePath) { Send-ProGetAsset -Session $Session ` -AssetPath $assetPath ` -FilePath $FilePath ` -MaxRequestSize $MaxRequestSize } } function Test-ProGetFeed { <# .SYNOPSIS Checks if a feed exists in a ProGet instance. .DESCRIPTION The `Test-ProGetFeed` function tests if a feed exists in ProGet instance. Pass the session to your ProGet instance to the `Session` parameter (use `New-ProGetSession` to create a session). Pass the name of the feed to the `Name` parameter. Pass the type of the feed to the `Type` parameter. If the feed exists, the function returns `true`. Otherwise, it returns `false`. Uses the `Feeds_GetFeed` endpoint in ProGet's native API. .EXAMPLE Test-ProGetFeed -Session $ProGetSession -Name 'Apps' -Type 'ProGet' Demonstrates how to call `Test-ProGetFeed`. In this case, a value of `$true` will be returned if a Universal package feed named 'Apps' exists. Otherwise, `$false` #> [CmdletBinding()] param( # The session includes ProGet's URI and the API key. Use `New-ProGetSession` to create session objects [Parameter(Mandatory)] [pscustomobject] $Session, # The feed name indicates the name of the package feed that will be created. [Parameter(Mandatory)] [Alias('FeedName')] [String] $Name, # The feed type indicates the type of package feed to create. # Valid feed types are ('VSIX', 'RubyGems', 'Docker', 'ProGet', 'Maven', 'Bower', 'npm', 'Deployment', 'Chocolatey', 'NuGet', 'PowerShell') - check here for a latest list - https://inedo.com/support/documentation/proget/feed-types/universal [Parameter(Mandatory)] [Alias('FeedType')] [String] $Type ) Set-StrictMode -Version 'Latest' if( !$Session.ApiKey) { Write-Error -Message ('Failed to test for package feed ''{0}/{1}''. This function uses the ProGet Native API, which requires an API key. When you create a ProGet session with `New-ProGetSession`, provide an API key via the `ApiKey` parameter' -f $FeedType, $FeedName) return } if( $Type -eq 'ProGet' ) { $msg = 'ProGet renamed its "ProGet" feed type name to "Universal". Please update the value of ' + 'New-ProGetFeed''s "Type" parameter from "ProGet" to "Universal".' Write-Warning $msg $Type = 'Universal' } $Parameters = @{ 'Feed_Name' = $Name; } $feed = Invoke-ProGetNativeApiMethod -Session $Session -Name 'Feeds_GetFeed' -Parameter $Parameters return ($feed -and ($feed | Get-Member -Name 'FeedType_Name') -and $feed.FeedType_Name -eq $Type) } # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every function/cmdlet call in your function. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. $Cmdlet, [Parameter(Mandatory = $true)] [Management.Automation.SessionState] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. $SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |