PSPortainer.psm1
#Region '.\prefix.ps1' 0 # The content of this file will be prepended to the top of the psm1 module file. This is useful for custom module setup is needed on import. #EndRegion '.\prefix.ps1' 2 #Region '.\Classes\PortainerContainerProcess.class.ps1' 0 class PortainerContainerProcess { [string]$UserID [int]$ProcessID [int]$ParentProcessID [int]$C [string]$STIME [string]$Terminal [timespan]$TIME [string]$Command PortainerContainerProcess () { $this.UserID = '' $this.ProcessID = 0 $this.ParentProcessID = 0 $this.C = 0 $this.STime = '' $this.Terminal = '' $this.Time = New-TimeSpan $this.Command = '' } PortainerContainerProcess ([string[]]$Object) { $this.UserID = $Object[0] $this.ProcessID = $Object[1] $this.ParentProcessID = $Object[2] $this.C = $Object[3] $this.STime = $Object[4] $this.Terminal = $Object[5] $this.Time = [timespan]::Parse($Object[6]) $this.Command = $Object[7] } [string] ToString () { return "ProcessID: $($this.ProcessID)" } } #EndRegion '.\Classes\PortainerContainerProcess.class.ps1' 41 #Region '.\Classes\PortainerSession.class.ps1' 0 [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'JWT token retreived in plain text')] class PortainerSession { [string]$BaseUri [string]$AuthMethod [securestring]$AccessToken [pscredential]$Credential [securestring]$JWT [string]$APIUri [string]$InstanceID [string]$PortainerVersion [string]$DefaultDockerEndpoint [string]$SessionID PortainerSession ([string]$BaseUri, [securestring]$AccessToken) { Write-Debug -Message 'PortainerSession.Class; Running constructor accesstoken' $this.SessionID = (New-Guid).Guid $this.BaseUri = $BaseUri $this.APIUri = "$BaseUri/api" $this.AuthMethod = 'AccessToken' $this.AccessToken = $AccessToken $this.GetStatus() $this.ResolveDockerEndpoint() Write-Verbose -Message "Connected to portainer instance at $($this.BaseUri) with AccessToken" } PortainerSession ([string]$BaseUri, [pscredential]$Credential) { Write-Debug -Message 'PortainerSession.Class; Running constructor credential' $this.SessionID = (New-Guid).Guid $this.BaseUri = $BaseUri $this.APIUri = "$BaseUri/api" $this.AuthMethod = 'Credential' $this.Credential = $Credential $this.AuthenticateCredential() $this.GetStatus() $this.ResolveDockerEndpoint() Write-Verbose -Message "Connected to portainer instance at $($this.BaseUri) with Credentials" } hidden ResolveDockerEndpoint () { [array]$AllEndpoints = InvokePortainerRestMethod -Method Get -RelativePath '/endpoints' -PortainerSession $this if ($AllEndpoints.Count -eq 1) { $this.DefaultDockerEndpoint = $AllEndpoints[0].Name } } hidden AuthenticateCredential() { $JWTResponse = InvokePortainerRestMethod -NoAuth -Method:'Post' -PortainerSession $this -RelativePath '/auth' -Body @{password = $this.Credential.GetNetworkCredential().Password; username = $this.Credential.Username } $this.JWT = ConvertTo-SecureString -String $JWTResponse.JWT -AsPlainText -Force Remove-Variable -Name JWTResponse } hidden GetStatus() { $Status = InvokePortainerRestMethod -NoAuth -Method:'Get' -PortainerSession $this -RelativePath '/status' $this.PortainerVersion = $Status.Version $this.InstanceID = $Status.InstanceID Remove-Variable -Name Status } } #EndRegion '.\Classes\PortainerSession.class.ps1' 66 #Region '.\private\Assert-FolderExist.ps1' 0 function Assert-FolderExist { <# .SYNOPSIS Verify and create folder .DESCRIPTION Verifies that a folder path exists, if not it will create it .PARAMETER Path Defines the path to be validated .EXAMPLE 'C:\Temp' | Assert-FolderExist This will verify that the path exists and if it does not the folder will be created #> [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [string] $Path ) process { $exists = Test-Path -Path $Path -PathType Container if (!$exists) { $null = New-Item -Path $Path -ItemType Directory } } } #EndRegion '.\private\Assert-FolderExist.ps1' 31 #Region '.\private\enums.ps1' 0 enum ContainerCondition { notrunning nextexit removed } #EndRegion '.\private\enums.ps1' 8 #Region '.\private\GetNonNullOrEmptyFromList.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "4a356917-cc37-4ba0-b54b-82ead703ac2a", "FILENAME": "GetNonNullOrEmptyFromList.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-26", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function GetNonNullOrEmptyFromList { <# .DESCRIPTION Selects a non null, non empty value from the provided array, if more than one is found it coniders the lowest value in the array with priority. .PARAMETER Array String array containing the values to evaluate, null and empty strings are allowed as these will not be considered for selection, efectievly filtering them out. .PARAMETER AskIfNoneIsFound Defines if the script should try and ask the user for a value instead of failing. .PARAMETER PropertyName If the parameter AskIfNoneIsFound is selected this string is used to ask the user for a value .EXAMPLE GetNonNullOrEmptyFromList -Array @('test1',$null,'','test2') The example above would return test1 as it is not null or empty and by beeing defined earlier in the array than test2. #> [CmdletBinding()] # Enabled advanced function support [OutputType([string])] param( [Parameter(Mandatory)][AllowNull()][AllowEmptyString()][string[]]$Array, [Parameter(Mandatory, ParameterSetName = 'Ask')][switch]$AskIfNoneIsFound, [Parameter(Mandatory, ParameterSetName = 'Ask')][string]$PropertyName ) $Selected = $null # Reverse array to process the first item as highest prio [array]::Reverse($Array) foreach ($item in $Array) { if (-not ([string]::IsNullOrEmpty($Item))) { $Selected = $item } } # Ask user if no cadidate can be selected if (-not $Selected -and $AskIfNoneIsFound) { $Selected = Read-Host -Prompt "Please enter a value for $PropertyName" } # Verify that user input is not empty before returning if ([string]::IsNullOrEmpty($Selected)) { Write-Error -Message 'GetNonNullOrEmptyFromList; Unable to find candidate' -ErrorAction Stop } else { return $Selected } } #EndRegion '.\private\GetNonNullOrEmptyFromList.ps1' 68 #Region '.\private\Invoke-GarbageCollect.ps1' 0 function Invoke-GarbageCollect { <# .SYNOPSIS Calls system.gc collect method. Purpose is mainly for readability. .DESCRIPTION Calls system.gc collect method. Purpose is mainly for readability. .EXAMPLE Invoke-GarbageCollect #> [system.gc]::Collect() } #EndRegion '.\private\Invoke-GarbageCollect.ps1' 13 #Region '.\private\InvokePortainerRestMethod.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "0cf86f27-137b-43ee-a194-ec0207d85ce5", "FILENAME": "InvokePortainerRestMethod.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-23", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function InvokePortainerRestMethod { <# .DESCRIPTION Function that is responsible for making the rest api web call .PARAMETER NoAuth Specifies that the REST API call do not need authentication .PARAMETER Method Defines the method to use when calling the REST API, valid values GET,POST etc. .PARAMETER PortainerSession A PortainerSession object to use for the call. .PARAMETER RelativePath The REST API path relative to the base URL .PARAMETER Body Defines body attributes for the REST API call .PARAMETER Headers Defines header attributes for the REST API call .EXAMPLE InvokePortainerRestMethod #> [CmdletBinding()] # Enabled advanced function support param( [switch]$NoAuth, [string]$Method, [portainersession]$PortainerSession, [string]$RelativePath, [hashtable]$Body = @{}, [hashtable]$Headers = @{} ) if (-not $PortainerSession) { Write-Debug -Message 'InvokePortainerRestMethod; No PortainerSession passed as parameter' if ($script:PortainerSession) { Write-Debug -Message 'InvokePortainerRestMethod; PortainerSession found in script scope' $PortainerSession = $script:PortainerSession } else { Write-Error -Message 'No Portainer Session established, please call Connect-Portainer' } } $InvokeRestMethodSplat = @{ Method = $Method Uri = "$($PortainerSession.ApiUri)$($RelativePath)" } if (-not $NoAuth) { switch ($PortainerSession.AuthMethod) { 'Credential' { $InvokeRestMethodSplat.Authentication = 'Bearer' $InvokeRestMethodSplat.Token = $PortainerSession.JWT } 'AccessToken' { $Headers.'X-API-Key' = (ConvertFrom-SecureString -SecureString $PortainerSession.AccessToken -AsPlainText) } } } if ($Headers.Keys.Count -gt 0) { $InvokeRestMethodSplat.Headers = $Headers } if ($InvokeRestMethodSplat.Method -eq 'Get') { if ($Body.Keys.Count -gt 0 ) { $InvokeRestMethodSplat.Body = $Body } } elseif ($InvokeRestMethodSplat.Method -eq 'Post') { # Might need to be changed, some post requests require formdata $InvokeRestMethodSplat.Body = $Body | ConvertTo-Json -Compress $InvokeRestMethodSplat.ContentType = 'application/json' } Write-Debug -Message "InvokePortainerRestMethod; Calling Invoke-RestMethod with settings`r`n$($InvokeRestMethodSplat | ConvertTo-Json)" Invoke-RestMethod @InvokeRestMethodSplat -Verbose:$false | ForEach-Object { $_ } } #endregion #EndRegion '.\private\InvokePortainerRestMethod.ps1' 102 #Region '.\private\pslog.ps1' 0 function pslog { <# .SYNOPSIS This is simple logging function that automatically log to file. Logging to console is maintained. .DESCRIPTION This is simple logging function that automatically log to file. Logging to console is maintained. .PARAMETER Severity Defines the type of log, valid vales are, Success,Info,Warning,Error,Verbose,Debug .PARAMETER Message Defines the message for the log entry .PARAMETER Source Defines a source, this is useful to separate log entries in categories for different stages of a process or for each function, defaults to default .PARAMETER Throw Specifies that when using severity error pslog will throw. This is useful in catch statements so that the terminating error is propagated upwards in the stack. .PARAMETER LogDirectoryOverride Defines a hardcoded log directory to write the log file to. This defaults to %appdatalocal%\<modulename\logs. .PARAMETER DoNotLogToConsole Specifies that logs should only be written to the log file and not to the console. .EXAMPLE pslog Verbose 'Successfully wrote to logfile' Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Sole purpose of function is logging, including console')] [cmdletbinding()] param( [parameter(Position = 0)] [ValidateSet('Success', 'Info', 'Warning', 'Error', 'Verbose', 'Debug')] [Alias('Type')] [string] $Severity, [parameter(Mandatory, Position = 1)] [string] $Message, [parameter(position = 2)] [string] $source = 'default', [parameter(Position = 3)] [switch] $Throw, [parameter(Position = 4)] [string] $LogDirectoryOverride, [parameter(Position = 5)] [switch] $DoNotLogToConsole ) begin { if (-not $LogDirectoryOverride) { $localappdatapath = [Environment]::GetFolderPath('localapplicationdata') # ie C:\Users\<username>\AppData\Local $modulename = $MyInvocation.MyCommand.Module $logdir = "$localappdatapath\$modulename\logs" } else { $logdir = $LogDirectoryOverride } $logdir | Assert-FolderExist -Verbose:$VerbosePreference $timestamp = (Get-Date) $logfilename = ('{0}.log' -f $timestamp.ToString('yyy-MM-dd')) $timestampstring = $timestamp.ToString('yyyy-MM-ddThh:mm:ss.ffffzzz') } process { switch ($Severity) { 'Success' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Host -Object "SUCCESS: $timestampstring`t$source`t$message" -ForegroundColor Green } } 'Info' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Information -MessageData "$timestampstring`t$source`t$message" } } 'Warning' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Warning -Message "$timestampstring`t$source`t$message" } } 'Error' { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false if (-not $DoNotLogToConsole) { Write-Error -Message "$timestampstring`t$source`t$message" } if ($throw) { throw } } 'Verbose' { if ($VerbosePreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Verbose -Message "$timestampstring`t$source`t$message" } } 'Debug' { if ($DebugPreference -ne 'SilentlyContinue') { "$timestampstring`t$psitem`t$source`t$message" | Add-Content -Path "$logdir\$logfilename" -Encoding utf8 -WhatIf:$false } if (-not $DoNotLogToConsole) { Write-Debug -Message "$timestampstring`t$source`t$message" } } } } } #EndRegion '.\private\pslog.ps1' 137 #Region '.\private\ResolveEndpointID.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "bc240721-1c04-45de-bc7b-3945f6220f0b", "FILENAME": "ResolveEndpointID.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function ResolveEndpointID { <# .DESCRIPTION Determines the source of endpoint and returns the endpoint ID .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE ResolveEndpointID #> [CmdletBinding()] param( [Parameter()][AllowNull()][AllowEmptyString()][string]$Endpoint, [Parameter()][PortainerSession]$Session = $null ) $EndpointName = GetNonNullOrEmptyFromList -Array @($Endpoint, $Session.DefaultDockerEndpoint) -AskIfNoneIsFound -PropertyName 'EndpointName' Write-Debug "ResolveEndpointID; Endpoint $EndpointName select" $EndpointId = Get-PEndpoint -SearchString $EndpointName | Select-Object -ExpandProperty Id if ($EndpointId) { return $EndpointID } else { throw 'No endpoint found' } } #EndRegion '.\private\ResolveEndpointID.ps1' 53 #Region '.\private\Write-PSProgress.ps1' 0 function Write-PSProgress { <# .SYNOPSIS Wrapper for PSProgress .DESCRIPTION This function will automatically calculate items/sec, eta, time remaining as well as set the update frequency in case the there are a lot of items processing fast. .PARAMETER Activity Defines the activity name for the progressbar .PARAMETER Id Defines a unique ID for this progressbar, this is used when nesting progressbars .PARAMETER Target Defines a arbitrary text for the currently processed item .PARAMETER ParentId Defines the ID of a parent progress bar .PARAMETER Completed Explicitly tells powershell to set the progress bar as completed removing it from view. In some cases the progress bar will linger if this is not done. .PARAMETER Counter The currently processed items counter .PARAMETER Total The total number of items to process .PARAMETER StartTime Sets the start datetime for the progressbar, this is required to calculate items/sec, eta and time remaining .PARAMETER DisableDynamicUpdateFrquency Disables the dynamic update frequency function and every item will update the status of the progressbar .PARAMETER NoTimeStats Disables calculation of items/sec, eta and time remaining .EXAMPLE 1..10000 | foreach-object -begin {$StartTime = Get-Date} -process { Write-PSProgress -Activity 'Looping' -Target $PSItem -Counter $PSItem -Total 10000 -StartTime $StartTime } Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Standard')] [Parameter(Mandatory = $true, Position = 0, ParameterSetName = 'Completed')] [string] $Activity, [Parameter(Position = 1, ParameterSetName = 'Standard')] [Parameter(Position = 1, ParameterSetName = 'Completed')] [ValidateRange(0, 2147483647)] [int] $Id, [Parameter(Position = 2, ParameterSetName = 'Standard')] [string] $Target, [Parameter(Position = 3, ParameterSetName = 'Standard')] [Parameter(Position = 3, ParameterSetName = 'Completed')] [ValidateRange(-1, 2147483647)] [int] $ParentId, [Parameter(Position = 4, ParameterSetname = 'Completed')] [switch] $Completed, [Parameter(Mandatory = $true, Position = 5, ParameterSetName = 'Standard')] [long] $Counter, [Parameter(Mandatory = $true, Position = 6, ParameterSetName = 'Standard')] [long] $Total, [Parameter(Position = 7, ParameterSetName = 'Standard')] [datetime] $StartTime, [Parameter(Position = 8, ParameterSetName = 'Standard')] [switch] $DisableDynamicUpdateFrquency, [Parameter(Position = 9, ParameterSetName = 'Standard')] [switch] $NoTimeStats ) # Define current timestamp $TimeStamp = (Get-Date) # Define a dynamic variable name for the global starttime variable $StartTimeVariableName = ('ProgressStartTime_{0}' -f $Activity.Replace(' ', '')) # Manage global start time variable if ($PSBoundParameters.ContainsKey('Completed') -and (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Remove the global starttime variable if the Completed switch parameter is users try { Remove-Variable -Name $StartTimeVariableName -ErrorAction Stop -Scope Global } catch { throw $_ } } elseif (-not (Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction SilentlyContinue)) { # Global variable do not exist, create global variable if ($null -eq $StartTime) { # No start time defined with parameter, use current timestamp as starttime Set-Variable -Name $StartTimeVariableName -Value $TimeStamp -Scope Global $StartTime = $TimeStamp } else { # Start time defined with parameter, use that value as starttime Set-Variable -Name $StartTimeVariableName -Value $StartTime -Scope Global } } else { # Global start time variable is defined, collect and use it $StartTime = Get-Variable -Name $StartTimeVariableName -Scope Global -ErrorAction Stop -ValueOnly } # Define frequency threshold $Frequency = [Math]::Ceiling($Total / 100) switch ($PSCmdlet.ParameterSetName) { 'Standard' { # Only update progress is any of the following is true # - DynamicUpdateFrequency is disabled # - Counter matches a mod of defined frequecy # - Counter is 0 # - Counter is equal to Total (completed) if (($DisableDynamicUpdateFrquency) -or ($Counter % $Frequency -eq 0) -or ($Counter -eq 1) -or ($Counter -eq $Total)) { # Calculations for both timestats and without $Percent = [Math]::Round(($Counter / $Total * 100), 0) # Define count progress string status $CountProgress = ('{0}/{1}' -f $Counter, $Total) # If percent would turn out to be more than 100 due to incorrect total assignment revert back to 100% to avoid that write-progress throws if ($Percent -gt 100) { $Percent = 100 } # Define write-progress splat hash $WriteProgressSplat = @{ Activity = $Activity PercentComplete = $Percent CurrentOperation = $Target } # Add ID if specified if ($Id) { $WriteProgressSplat.Id = $Id } # Add ParentID if specified if ($ParentId) { $WriteProgressSplat.ParentId = $ParentId } # Calculations for either timestats and without if ($NoTimeStats) { $WriteProgressSplat.Status = ('{0} - {1}%' -f $CountProgress, $Percent) } else { # Total seconds elapsed since start $TotalSeconds = ($TimeStamp - $StartTime).TotalSeconds # Calculate items per sec processed (IpS) $ItemsPerSecond = ([Math]::Round(($Counter / $TotalSeconds), 2)) # Calculate seconds spent per processed item (for ETA) $SecondsPerItem = if ($Counter -eq 0) { 0 } else { ($TotalSeconds / $Counter) } # Calculate seconds remainging $SecondsRemaing = ($Total - $Counter) * $SecondsPerItem $WriteProgressSplat.SecondsRemaining = $SecondsRemaing # Calculate ETA $ETA = $(($Timestamp).AddSeconds($SecondsRemaing).ToShortTimeString()) # Add findings to write-progress splat hash $WriteProgressSplat.Status = ('{0} - {1}% - ETA: {2} - IpS {3}' -f $CountProgress, $Percent, $ETA, $ItemsPerSecond) } # Call writeprogress Write-Progress @WriteProgressSplat } } 'Completed' { Write-Progress -Activity $Activity -Id $Id -Completed } } } #EndRegion '.\private\Write-PSProgress.ps1' 214 #Region '.\public\Container\Get-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "995b4fdc-f68c-41a3-b9d3-73219c3086e3", "FILENAME": "Get-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-23", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PContainer { <# .DESCRIPTION Retreives docker containers .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PContainer Retreives all containers from the endpoint configured on the portainer session default docker endpoint setting. .EXAMPLE Get-PContainer -Id "<id>" Retreives a single container object with the specified Id .EXAMPLE Get-PContainer -Endpoint 'prod' Retreives all containers on the prod endpoint .EXAMPLE Get-PContainer -Session $Session Retreives all containers on the portainer instance defined #> [CmdletBinding(DefaultParameterSetName = 'list')] param( [Parameter()][string]$Endpoint, [Parameter(ParameterSetName = 'id', ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session if ($PSCmdlet.ParameterSetName -eq 'list') { [array]$Id = InvokePortainerRestMethod -Method Get -RelativePath "/endpoints/$EndpointId/docker/containers/json" -PortainerSession:$Session -Body @{all = $true } | Select-Object -ExpandProperty Id } } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } InvokePortainerRestMethod -Method Get -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/json" -PortainerSession:$Session | ForEach-Object { $PSItem.PSobject.TypeNames.Insert(0, 'PortainerContainer'); $_ } } } } #EndRegion '.\public\Container\Get-PContainer.ps1' 90 #Region '.\public\Container\Get-PContainerProcess.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "8e1ee3eb-e9b1-459e-a631-a9c9d1be4ce6", "FILENAME": "Get-PContainerProcess.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-25", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PContainerProcess { <# .DESCRIPTION Get processes running inside the container .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PContainer -Id '<id>' | Get-PContainerProcess Retreives the running processes in the specified container #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Session', Justification = 'False positive')] [CmdletBinding()] # Enabled advanced function support param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } InvokePortainerRestMethod -Method Get -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/top" -PortainerSession:$Session | Select-Object -expand processes | ForEach-Object { [PortainerContainerProcess]::New($PSItem) } } } } #EndRegion '.\public\Container\Get-PContainerProcess.ps1' 73 #Region '.\public\Container\Get-PContainerStatistic.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "affcc338-63ee-4808-aaf4-3e6afc562eb0", "FILENAME": "Get-PContainerStatistic.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-27", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PContainerStatistic { <# .DESCRIPTION Retreives container statistics .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PContainer -Id '<id>' | Get-PContainerStatistic #> [CmdletBinding()] # Enabled advanced function support param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } InvokePortainerRestMethod -Method Get -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/stats" -PortainerSession:$Session -Body @{'stream' = $false; 'one-shot' = $true } } } } #endregion #EndRegion '.\public\Container\Get-PContainerStatistic.ps1' 74 #Region '.\public\Container\Rename-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "d9022235-1976-48ee-94b0-93f768db398f", "FILENAME": "Rename-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Rename-PContainer { <# .DESCRIPTION Rename a container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .PARAMETER NewName Defines the new name of the container .EXAMPLE Resize-PContainerTTY #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'NewName', Justification = 'False positive')] [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null, [Parameter()][string]$NewName ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Rename')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/rename" -PortainerSession:$Session -Body @{ name = $NewName } } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } elseif ($_.Exception.Message -like '*409*') { Write-Error -Message "Name $NewName is already in use" } else { Write-Error -Message "Failed to rename container with id <$ContainerID> with error: $_" } } } } } } #EndRegion '.\public\Container\Rename-PContainer.ps1' 95 #Region '.\public\Container\Resize-PContainerTTY.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "6ca5dda3-397a-4b2e-abac-8d0cf903de5f", "FILENAME": "Resize-PContainerTTY.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Resize-PContainerTTY { <# .DESCRIPTION Resizes the TTY for a container .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .PARAMETER Height Defines the height of the TTY session .PARAMETER Width Defines the width of the TTY session .EXAMPLE Resize-PContainerTTY #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Height', Justification = 'False positive')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Width', Justification = 'False positive')] [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null, [Parameter()][int]$Height, [Parameter()][int]$Width ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Resize TTY')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/resize" -PortainerSession:$Session -Body @{h = $Height; w = $Width } } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to resize container TTY with id <$ContainerID> with error: $_" } } } } } } #EndRegion '.\public\Container\Resize-PContainerTTY.ps1' 95 #Region '.\public\Container\Restart-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "32e718aa-b4cf-4a35-bd12-36853ed90e7b", "FILENAME": "Restart-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-28", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Restart-PContainer { <# .DESCRIPTION Restart container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Restart-PContainer Description of example #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Restart')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/restart" -PortainerSession:$Session } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to restart container with id <$ContainerID> with error: $_" } } } } } } #endregion #EndRegion '.\public\Container\Restart-PContainer.ps1' 89 #Region '.\public\Container\Resume-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "a25cd92a-ef1d-495d-a555-7ece51f720eb", "FILENAME": "Resume-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Resume-PContainer { <# .DESCRIPTION Resumes a container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Restart-PContainer Description of example #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Resume')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/unpause" -PortainerSession:$Session } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to resume container with id <$ContainerID> with error: $_" } } } } } } #EndRegion '.\public\Container\Resume-PContainer.ps1' 88 #Region '.\public\Container\Start-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "97a4ab27-c8c2-4715-aa10-3cf73e2b5eea", "FILENAME": "Start-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-28", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Start-PContainer { <# .DESCRIPTION Starts a container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Start-PContainer Description of example #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Start')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/start" -PortainerSession:$Session } catch { if ($_.Exception.Message -like '*304 (Not Modified)*') { Write-Warning -Message "Container <$ContainerID> is already started" } elseif ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to start container with id <$ContainerID> with error: $_" } } } } } } #endregion #EndRegion '.\public\Container\Start-PContainer.ps1' 93 #Region '.\public\Container\Stop-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "e2180652-5c6b-4b37-97ce-e7beb7d067b9", "FILENAME": "Stop-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-28", "COMPANYNAME": [], "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Stop-PContainer { <# .DESCRIPTION Stop a container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .PARAMETER Kill Defines that the container should be stopped with the kill command rather than a graceful shutdown .EXAMPLE Stop-PContainer Description of example #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Kill', Justification = 'False positive')] [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null, [Parameter()][switch]$Kill ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Stop')) { if ($Kill) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/kill" -PortainerSession:$Session -Body @{signal = 'SIGKILL' } } catch { if ($_.Exception.Message -like '*409*') { Write-Warning -Message "Container <$ContainerID> is already stopped" } elseif ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to stop container with id <$ContainerID> with error: $_" } } } else { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/stop" -PortainerSession:$Session } catch { if ($_.Exception.Message -like '*304 (Not Modified)*') { Write-Warning -Message "Container <$ContainerID> is already stopped" } elseif ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to stop container with id <$ContainerID> with error: $_" } } } } } } } #endregion #EndRegion '.\public\Container\Stop-PContainer.ps1' 122 #Region '.\public\Container\Suspend-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "d9c10133-746b-422f-8752-3ceb77cb53c9", "FILENAME": "Suspend-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Suspend-PContainer { <# .DESCRIPTION Pauses a container in portainer .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Restart-PContainer Description of example #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } if ($PSCmdlet.ShouldProcess($ContainerID, 'Suspend')) { try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/pause" -PortainerSession:$Session } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } else { Write-Error -Message "Failed to suspend container with id <$ContainerID> with error: $_" } } } } } } #EndRegion '.\public\Container\Suspend-PContainer.ps1' 88 #Region '.\public\Container\Wait-PContainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "fa217982-f766-4b75-9199-a628cb201837", "FILENAME": "Wait-PContainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-30", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Wait-PContainer { <# .DESCRIPTION Wait for container to stop .PARAMETER Endpoint Defines the portainer endpoint to use when retreiving containers. If not specified the portainer sessions default docker endpoint value is used. Use Get-PSession to see what endpoint is selected Use Select-PEndpoint to change the default docker endpoint in the portainer session. -Endpoint 'local' .PARAMETER Id Defines the id of the container to retreive. -Id '<Id>' .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .PARAMETER Condition Defines the condition that we are waiting for, valid values are notrunning, nextexit, removed .EXAMPLE Restart-PContainer Description of example #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', 'Condition', Justification = 'False positive')] [CmdletBinding()] param( [Parameter()][string]$Endpoint, [Parameter(ValueFromPipeline)][object[]]$Id, [Parameter()][PortainerSession]$Session = $null, [Parameter()][ContainerCondition]$Condition = 'notrunning' ) BEGIN { $Session = Get-PSession -Session:$Session $EndpointID = ResolveEndpointID -Endpoint:$Endpoint -Session:$Session } PROCESS { $Id | ForEach-Object { if ($PSItem.PSObject.TypeNames -contains 'PortainerContainer') { $ContainerID = $PSItem.Id } elseif ($PSItem.GetType().Name -eq 'string') { $ContainerID = $PSItem } else { Write-Error -Message 'Cannot determine input object type' -ErrorAction Stop } try { InvokePortainerRestMethod -Method POST -RelativePath "/endpoints/$EndpointId/docker/containers/$ContainerID/wait" -PortainerSession:$Session -body @{condition = $Condition } } catch { if ($_.Exception.Message -like '*404*') { Write-Error -Message "No container with id <$ContainerID> could be found" } elseif ($_.Exception.Message -like '*400*') { Write-Error -Message 'Bad parameter' } else { Write-Error -Message "Failed to initiate wait for container with id <$ContainerID> with error: $_" } } } } } #EndRegion '.\public\Container\Wait-PContainer.ps1' 92 #Region '.\public\Endpoint\Get-PEndpoint.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "82e7ab5b-b8c1-464c-8ce9-8593f8949caf", "FILENAME": "Get-PEndpoint.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-24", "COMPANYNAME": "\"GetPS\"", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PEndpoint { <# .DESCRIPTION Retreives available endpoints .PARAMETER SearchString Defines a searchstring to use for filtering endpoints .PARAMETER Id Defines the Id of the endpoint to retreive. .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PEndpoint -SearchString 'local' Retreives all endpoints containing the word local #> [CmdletBinding(DefaultParameterSetName = 'List')] param( [Parameter(ParameterSetName = 'Search')][string]$SearchString, [Parameter(ParameterSetname = 'Id')][int]$Id, [Parameter()][PortainerSession]$Session = $null # Does not work for some reason, regardless of input to the API parameter name, all endpoints are returned... #[Parameter(ParameterSetName = 'Name')][string]$Name ) switch ($PSCmdlet.ParameterSetName) { 'List' { InvokePortainerRestMethod -Method Get -RelativePath '/endpoints' -PortainerSession:$Session } 'Search' { InvokePortainerRestMethod -Method Get -RelativePath '/endpoints' -Body @{search = $SearchString } -PortainerSession:$Session } 'Id' { InvokePortainerRestMethod -Method Get -RelativePath "/endpoints/$Id" -PortainerSession:$Session } <# 'Name' { InvokePortainerRestMethod -Method Get -RelativePath '/endpoints' -Body @{name = $Name } -PortainerSession:$Session } #> } } #endregion #EndRegion '.\public\Endpoint\Get-PEndpoint.ps1' 65 #Region '.\public\Endpoint\Select-PEndpoint.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "6e22beb1-0b30-4e81-87ee-67e10c3410f5", "FILENAME": "Select-PEndpoint.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-24", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Select-PEndpoint { <# .DESCRIPTION Configures the default endpoint to use .PARAMETER Endpoint Defines the endpoint name to select .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Select-PEndpoint -Endpoint 'prod' Set the default endpoint to use #> [CmdletBinding()] param( [Parameter()][string]$Endpoint, [Parameter()][PortainerSession]$Session = $null ) if ($Session) { $Session.DefaultDockerEndpoint = $Endpoint } elseif ($script:PortainerSession) { $script:PortainerSession.DefaultDockerEndpoint = $Endpoint } else { Write-Warning 'No session found' } } #endregion #EndRegion '.\public\Endpoint\Select-PEndpoint.ps1' 52 #Region '.\public\Portainer\Connect-Portainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "a81b1e93-5997-4aeb-b491-524b7a309862", "FILENAME": "Connect-Portainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-23", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Connect-Portainer { <# .DESCRIPTION Connect to a Portainer instance .PARAMETER BaseURL Defines the base URL to the portainer instance -BaseURL 'https://portainer.contoso.com' .PARAMETER AccessToken Connects to portainer using a access token. This AccessToken can be generated from the Portainer Web GUI. -AccessToken 'ptr_ABoR54bB1NUc4aNY0F2PhppP1tVDu2Husr3vEbPUsw5' .PARAMETER Credential Connect to portainer using username and password. Parameter accepts a PSCredentials object -Credential (Get-Credential) .PARAMETER PassThru This parameter will cause the function to return a PortainerSession object that can be stored in a variable and referensed with the -Session parameter on most cmdlets. -PassThru .EXAMPLE Connect-Portainer -BaseURL 'https://portainer.contoso.com' -AccessToken 'ptr_ABoR54bB1NUc4aNY0F2PhppP1tVDu2Husr3vEbPUsw5=' Connect using access token .EXAMPLE Connect-Portainer -BaseURL 'https://portainer.contoso.com' -Credentials (Get-Credential) Connect using username and password #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'AccessToken')] [CmdletBinding()] param( [Parameter(Mandatory)][string]$BaseURL, [Parameter(ParameterSetName = 'AccessToken')][string]$AccessToken, [Parameter(ParameterSetName = 'Credentials')][pscredential]$Credential, [switch]$PassThru ) switch ($PSCmdlet.ParameterSetName) { 'AccessToken' { $AccessTokenSS = ConvertTo-SecureString -String $AccessToken -AsPlainText -Force Remove-Variable -Name AccessToken $script:PortainerSession = [PortainerSession]::New($BaseURL, $AccessTokenSS) } 'Credentials' { $script:PortainerSession = [PortainerSession]::New($BaseURL, $Credential) } } if ($Passthru) { return $script:PortainerSession } } #EndRegion '.\public\Portainer\Connect-Portainer.ps1' 71 #Region '.\public\Portainer\Disconnect-Portainer.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "2b51d232-02ef-490b-8ab1-4856de857152", "FILENAME": "Disconnect-Portainer.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-24", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Disconnect-Portainer { <# .DESCRIPTION Disconnect and cleanup session configuration .PARAMETER Session Defines a PortainerSession object that will be disconnected and cleaned up. .EXAMPLE Disconnect-Portainer Disconnect from the default portainer session .EXAMPLE Disconnect-Portainer -Session $Session Disconnect the specified session #> [CmdletBinding()] param( [Parameter()][PortainerSession]$Session = $null ) InvokePortainerRestMethod -Method Post -RelativePath '/auth/logout' -PortainerSession:$Session # Remove PortainerSession variable if ($Session) { if ($script:PortainerSession.SessionID -eq $Session.SessionID) { Remove-Variable PortainerSession -Scope Script } } else { Remove-Variable PortainerSession -Scope Script } } #EndRegion '.\public\Portainer\Disconnect-Portainer.ps1' 50 #Region '.\public\Portainer\Get-PSession.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "9a38dcb2-9a2b-45fc-b938-b36e5a96e4d4", "FILENAME": "Get-PortainerSession.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-24", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PSession { <# .DESCRIPTION Displays the Portainer Session object. .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PSession Returns the PortainerSession, if none is specified, it tries to retreive the default #> [CmdletBinding()] param( [Parameter()][PortainerSession]$Session = $null ) if ($Session) { Write-Debug -Message 'Get-PSession; PortainerSession was passed as parameter' return $Session } elseif ($script:PortainerSession) { Write-Debug -Message 'Get-PSession; PortainerSession found in script scope' return $script:PortainerSession } else { Write-Error -Message 'No Portainer Session established, please call Connect-Portainer' } } #endregion #EndRegion '.\public\Portainer\Get-PSession.ps1' 50 #Region '.\public\Portainer\Get-PSettingsPublic.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "996ce218-a4b8-42ed-9e92-58f2c379685e", "FILENAME": "Get-PSettingsPublic.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-23", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PSettingsPublic { <# .DESCRIPTION Retreives Portainer Public Settings .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PSettingsPublic Retreives Portainer Public Settings #> [CmdletBinding()] param( [Parameter()][PortainerSession]$Session = $null ) InvokePortainerRestMethod -NoAuth -Method Get -RelativePath '/settings/public' -PortainerSession:$Session } #endregion #EndRegion '.\public\Portainer\Get-PSettingsPublic.ps1' 38 #Region '.\public\Portainer\Get-PStatus.ps1' 0 <#PSScriptInfo { "VERSION": "1.0.0", "GUID": "8293c6f9-b146-4ce4-bccc-de87f9d6b27a", "FILENAME": "Get-PStatus.ps1", "AUTHOR": "Hannes Palmquist", "CREATEDDATE": "2022-10-23", "COMPANYNAME": "GetPS", "COPYRIGHT": "(c) 2022, Hannes Palmquist, All Rights Reserved" } PSScriptInfo#> function Get-PStatus { <# .DESCRIPTION Get public status for portainer instance .PARAMETER Session Optionally define a portainer session object to use. This is useful when you are connected to more than one portainer instance. -Session $Session .EXAMPLE Get-PStatus Get public status for portainer instance #> [CmdletBinding()] param( [Parameter()][PortainerSession]$Session = $null ) InvokePortainerRestMethod -NoAuth -Method Get -RelativePath '/status' -PortainerSession:$Session } #endregion #EndRegion '.\public\Portainer\Get-PStatus.ps1' 37 #Region '.\suffix.ps1' 0 # The content of this file will be appended to the top of the psm1 module file. This is useful for custom procesedures after all module functions are loaded. # # Argument completers # $AC_Endpoints = [scriptblock] { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'Parameter is automatically provided by powershell when argument completer is invoked regardless of weather it is used or not')] [OutputType([System.Management.Automation.CompletionResult])] param( [string] $CommandName, [string] $ParameterName, [string] $WordToComplete, [System.Management.Automation.Language.CommandAst] $CommandAst, [System.Collections.IDictionary] $FakeBoundParameters ) $CompletionResults = [System.Collections.Generic.List[System.Management.Automation.CompletionResult]]::new() Get-PEndpoint | ForEach-Object { $CompletionResults.Add( [System.Management.Automation.CompletionResult]::New(($_.Name)) ) } return $CompletionResults } Register-ArgumentCompleter -CommandName 'Get-PContainer' -Parameter 'Endpoint' -ScriptBlock $AC_Endpoints Register-ArgumentCompleter -CommandName 'Select-PEndpoint' -Parameter 'Endpoint' -ScriptBlock $AC_Endpoints #EndRegion '.\suffix.ps1' 30 # SIG # Begin signature block # MIIbmAYJKoZIhvcNAQcCoIIbiTCCG4UCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUAO1xxSThPZnKW9HQ8zWyewZt # ovKgghYPMIIDBDCCAeygAwIBAgIQFqvAHPTHupdKm+zXSmZcFTANBgkqhkiG9w0B # AQUFADAaMRgwFgYDVQQDDA9IYW5uZXNQYWxtcXVpc3QwHhcNMjExMTA2MjEzODU5 # WhcNMjYxMTA2MjE0ODU5WjAaMRgwFgYDVQQDDA9IYW5uZXNQYWxtcXVpc3QwggEi # MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6WNXwWLjX4EAPDxg9U54O83Sr # Vdf41bEPJz8V0wDQm4MJnnBGZTCqPBENXsfSbIPhHwad+tcsd3nc1ZNuQEJTV3W1 # pXTOtNK+yV0r51VSJvFYYo3qXFyvWdi9n8Z8/yydoWy1vXkx7CsbmIuniODGEzIE # C1z0QYgrjyNmXw1CewOjX+oW2cgjiJqjuxsS/sLnmqAOMOeV3047GnEEKTyderWR # L/U+ohEu2WcMao1UCKEtUmPo5rncRyHjY7wzwevby1I8oLgHWm8NlH9K2Kp43tx9 # +Mcpn64hTYW+jd7VCFmrEIDfZEWdY5EBfJZDwnol0vXvFPl+7E+WaCtKe6zNAgMB # AAGjRjBEMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNV # HQ4EFgQU0+0dMijn0qYkRTkT8lp0KErfo+swDQYJKoZIhvcNAQEFBQADggEBACoT # mkcM6C7Atq/ZzwVOH0A1qd5t1KsnAX393xLk73ssnmOW/c4P2fcoeEmQ8Idk6FJf # v+D6MkZIRb7PefqTCVhgi2ZZynNCWE2zqt5VxEZIPiG1Amke/OhEI8fxL/01HWZj # kGZD3cquXMA3SGS86Y7tX2S31/rAG812ew57ghfOL3EUPfBwCrSKV679ncNL0Td9 # AKiK2pUraM32/UppF7kEZlSTPtPwCPSp+xihf6ZRJM95gB8bSa+K/1wd8CD9f6Ng # XkyiSesaUjMhfh+hhg8otqB1ZqvsqsNtsibKo1IAHWWNDxa3sv8g4rErTQFARx4p # 3pksn4fYdZXhQzOHMtIwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0G # CSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0 # IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5 # NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNV # BAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQg # Um9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvk # XUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdt # HauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu # 34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0 # QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2 # kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM # 1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmI # dph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZ # K37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72 # gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqs # X40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyh # HsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8E # BTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAW # gBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUH # AQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYI # KwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz # c3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAE # CjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX # 979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offy # ct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3 # J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0 # d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6ts # ds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQw # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGwDCCBKigAwIBAgIQ # DE1pckuU+jwqSj0pB4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEX # MBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0 # ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAw # MDAwMFoXDTMzMTEyMTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERp # Z2lDZXJ0MSQwIgYDVQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSF # dDMaJqzQHFUeHjZtvJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWv # M+xhiummKNuQY1y9iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyI # xvG+4C99O7HKU41Agx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3U # TZWEaOOAy2p50dIQkUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyV # R4aFeT4MXmaMGgokvpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQ # ln5N4d3CraV++C0bH+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq # 5Xwx5/PCUsTmFntafqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk # +lbP4PQK5hRtZHi7mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl # 5S4pkKa3YWT62SBsGFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7 # ucxnEweawXjtxojIsG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076 # XepFcxyEftfO4tQ6dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1Ud # EwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZn # gQwBBAIwCwYJYIZIAYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCP # nshvMB0GA1UdDgQWBBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+g # TaBLhklodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRS # U0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCB # gDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUF # BzAChkxodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVk # RzRSU0E0MDk2U0hBMjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUA # A4ICAQBVqioa80bzeFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4 # iGNVCUY5APxp1MqbKfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIr # UPwbtZ4IMAn65C3XCYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk # /9+dEKfrALpfSo8aOlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+Y # siaVOBmIRBTlClmia+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YB # ZJwAwuladHUNPeF5iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD # +5sTX2q1x+DzBcNZ3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQ # RWAzgOAj3vgDpPZFR+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+b # vdgcmlHEL5r2X6cnl7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTo # ntRamMifv427GFxD9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/ # Otrl5fbmm9x+LMz/F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDGCBPMwggTvAgEB # MC4wGjEYMBYGA1UEAwwPSGFubmVzUGFsbXF1aXN0AhAWq8Ac9Me6l0qb7NdKZlwV # MAkGBSsOAwIaBQCgeDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3 # DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEV # MCMGCSqGSIb3DQEJBDEWBBSy2MsLu7MfhPeYM3DxW6LY0j66SzANBgkqhkiG9w0B # AQEFAASCAQAchF7sFqwGI9bAKdnwa/podcPvdT7QEbPsDTKmkrQ/G7aN2AlNanV0 # E7PvIV53z5S3D0S15qTzgItPkNxavBgj0vW39g6dKnb0IdqvP7AGDuv8HC65qPzP # CuyPzVrAhnbROurvKYEQ0Pumr8XDdAglGaymV60gopAjhhwShP3QyltmeX58MSOG # JTnBau3LcGXKTbH130rzvlskzIYdUdEEF9EWX3YtkjFF/UkFKyefwmEpvjVoCyUr # r3krJLcqmulw6vsnBLpnch5879KTiKC4/dNJ/ZLUdWBvNG+K/10RqUztVpf1baoy # VCiVBsOE/oxvUFdke2e+CAfQWyF7mL+LoYIDIDCCAxwGCSqGSIb3DQEJBjGCAw0w # ggMJAgEBMHcwYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu # MTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRp # bWVTdGFtcGluZyBDQQIQDE1pckuU+jwqSj0pB4A9WjANBglghkgBZQMEAgEFAKBp # MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTIyMTEw # OTE5MjIzNlowLwYJKoZIhvcNAQkEMSIEIHNuhcaLVeiBZny/J8Wgai3nVdss59Bi # wQEvPP/pVMZJMA0GCSqGSIb3DQEBAQUABIICALXKf7pNi/Y8jploIZtGbjLyrtlH # +21xp+IgePj0ImGdk2Dxts5ZqN7Tx1/3GY0eRV95RKXGc2tVJyC4ut3z9ARYrMpz # jaahAWT58VpnuYZncdoMHPLTgDekeO41z3knXQyyiEk5vxm6k6XRgjZOmymtCnRx # Z37vv0paQCy8WoRUWVv8aFV2xBaOvdkJsPYN73xTVW3r+BU9XyyNFGMjZg/WLdCX # IrSCRmxSUmfekVk7cb07oF9f1qIi4Xw+6LQLZK0WaBMCE5dpMYjnnRMmPeWSTrVh # J7qmx7POOql/mL8Zs7J9AGhrN/wM/EySLZDMAG2863R/40sOGmRv3RUuBuwTo94B # Fhsl21zBQuQBj+ofvU9K52HDAjhX+hoH1RRPO0efH4CyWBZN5FCZ5soH6b9tq1fA # tj6Bod0W3T9ruEMmCVDi1nwz14fU8v7ncczdHY+GdbFJwVxkpgT2Uu3BYPuaoMd8 # oQMYIkfBT/3GNPGRlbGoIfmEWFuyk2HsGgYRrEJHbCeFPOySEljNjqb9nxz6YQgV # cbzt4QFq2TTQ3Mn50XzLXUaq8vWyHgKbyPe4tzxdwIq0h4JxcTkdmhLSz+YG1CNv # hnlkk/nwysdkYtfazsfhabBY+LtH/5lzO6fOqRhkdfR8X6SAQ4E5bGKKQXF6ftuZ # Z8AenBfsKV/V1X4t # SIG # End signature block |