Rundeck.psm1
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # + Library # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ class RundeckApiData { # Function name [ValidateNotNullOrEmpty()] [System.String] $Name # Description for for this function [System.String] $Description # Action/method used for the Rundeck API web request [ValidateSet( 'Delete', 'Get', 'Patch', 'Post', 'Put' )] [System.String] $Method # List of endpoints available for this function [System.Collections.ArrayList] $Endpoints # Dictionary of available options [System.Collections.Generic.Dictionary[string, string]] $Body # Success code expected in the response. [ValidateSet( 200, 201, 202, 204 )] [System.UInt16] $SuccessCode # Rundeck Session hidden [System.Object]$Session # Constructor - Initialize data object RundeckApiData( [System.object] $Session, [System.String] $Function ) { $this.Name = $Function $this.Session = $Session $this.GetConfiguration() } [System.String]GetConfigurationFile() { $filePath = Join-Path -Path $script:ModulePath -ChildPath 'Config/ApiData.json' if( test-Path -Path $filePath ) { return $filePath } else { throw "Could not find API configuration file : ${filePath}" } } [System.Void]GetConfiguration() { $api = Get-Content -Path $this.GetConfigurationFile() | ConvertFrom-Json -ErrorAction 'Stop' if ( $null -eq ( $api | Get-Member -Name $this.Name -ErrorAction 'SilentlyContinue' ) ) { throw "Invalid endpoint: $($this.Name)" } $this.Description = $api.($this.Name).Description $this.Method = $api.($this.Name).Method $this.Endpoints = $api.($this.Name).Endpoints $this.SuccessCode = $api.($this.Name).SuccessCode # Convert PSCustomObject to Dictionary $this.Body = [System.Collections.Generic.Dictionary[string, string]]::new() $testbody = $api.($this.Name).Body.psobject.Members | where-object membertype -eq 'noteproperty' foreach ($bodyitem in $testbody) { $this.Body.Add($bodyitem.Name, $bodyitem.Value) } } [System.String]ConvertParameterToJsonBody($Parameters) { $bodyJson = @{} foreach ($Parameter in $Parameters.GetEnumerator()) { if ($this.body.ContainsKey($Parameter.Key)) { $bodyJson[$this.body.($Parameter.Key)] = $Parameter.Value } } return ( $bodyJson | ConvertTo-Json ) } [System.String]GetEndpoint() { return $this.Endpoints.Where( { $_ -notmatch '{id}' } ) } [System.String]GetEndpoint([System.String]$First) { $uri = $this.Endpoints.Where( { $_ -match '/{id}' -and $_ -notmatch '/{id}.*/{id}' } ) return ($uri -replace '{id}', $First) } [System.String]GetEndpoint([System.String]$First, [System.String]$Second) { $uri = $this.Endpoints.Where( { $_ -match '/{id}.*/{id}' } ) $uri = $uri -replace '(.*?)/{id}(.*)', "`$1/$( $First )`$2" return ( $uri -replace '{id}', $Second ) } [System.String]GetEndpointUri() { $uriBuilder = [System.UriBuilder]::new($this.Session.Uri) $uriBuilder.Path = $this.GetEndpoint() return $uriBuilder.ToString() } [System.String]GetEndpointUri([System.String]$First) { $uriBuilder = [System.UriBuilder]::new($this.Session.Uri) $uriBuilder.Path = $this.GetEndpoint($first) return $uriBuilder.ToString() } [System.String]GetEndpointUri([System.String]$First, [System.String]$Second) { $uriBuilder = [System.UriBuilder]::new($this.Session.Uri) $uriBuilder.Path = $this.GetEndpoint($first, $Second) return $uriBuilder.ToString() } } class RundeckSession { [ValidateSet( 'http', 'https' )] [System.String] $Protocol [ValidateNotNullOrEmpty()] [System.String] $Server [ValidateRange( 1, 65535 )] [System.UInt16] $Port = 443 [ValidateRange( 1, 30 )] [System.UInt16] $SessionLengthInMinutes [ValidateNotNull()] [System.DateTime] $SessionStartTime [ValidateNotNull()] [System.DateTime] $SessionExpirationTime [System.String] $Uri [ValidateSet( 'X-Rundeck-Auth-Token' )] Hidden [System.String] $AuthenticationHeader = 'X-Rundeck-Auth-Token' [ValidateSet( 'application/json' )] Hidden [System.String] $MediaType = 'application/json' [ValidateNotNull()] Hidden [Hashtable] $Headers = @{} RundeckSession ( [System.String] $Protocol, [System.String] $Server, [System.UInt16] $Port ) { $this.Protocol = $Protocol $this.Server = $Server $this.Port = $Port $this.Headers.Add( 'Accept', $this.MediaType ) $this.Headers.Add( 'Content-Type', $this.MediaType ) $uriBuilder = [System.UriBuilder]::new() $uriBuilder.Scheme = $Protocol $uriBuilder.Host = $Server $uriBuilder.Port = $Port $this.Uri = $uriBuilder.ToString() } [System.Void] Authorize ( [System.String] $AccessToken, [System.UInt16] $SessionLengthInMinutes ) { if ( $AccessToken -notmatch '^[a-z0-9]{32}$' ) { throw "Invalid access token: '${AccessToken}'." } $now = Get-Date $this.SessionStartTime = $now $this.SessionLengthInMinutes = $SessionLengthInMinutes $this.SessionExpirationTime = $now.AddMinutes( $this.SessionLengthInMinutes ) $this.Headers.($this.AuthenticationHeader) = $AccessToken } [System.Boolean] AuthorizationExists () { if ( $this.Headers.ContainsKey($this.AuthenticationHeader) ) { return $true } else { return $false } } [System.Boolean] IsActive () { if ( $this.SessionExpirationTime -gt ( Get-Date ) ) { return $true } else { return $false } } [System.Int32] GetMinutesRemaining () { [System.Int32] $return = ( $this.SessionExpirationTime - ( Get-Date ) ).Minutes return $return } [System.String] GetToken () { if ( $this.Headers.ContainsKey( $this.AuthenticationHeader ) ) { return ( $this.Headers.( $this.AuthenticationHeader ) ) } else { throw 'The session has not been authorized.' } } } # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # + Private Function # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ function Add-RundeckObjectDetail { <# .SYNOPSIS Decorate an object with - A TypeName - New properties - Default parameters .DESCRIPTION Helper function to decorate an object with - A TypeName - New properties - Default parameters .PARAMETER InputObject Object to decorate. Accepts pipeline input. .PARAMETER TypeName Typename to insert. This will show up when you use Get-Member against the resulting object. .PARAMETER PropertyToAdd Add these noteproperties. Format is a hashtable with Key (Property Name) = Value (Property Value). Example to add a One and Date property: -PropertyToAdd @{ One = 1 Date = (Get-Date) } .PARAMETER DefaultProperties Change the default properties that show up .PARAMETER Passthru Whether to pass the resulting object on. Defaults to true .EXAMPLE # # Create an object to work with $Object = [PSCustomObject]@{ First = 'Cookie' Last = 'Monster' Account = 'CMonster' } #Add a type name and a random property Add-ObjectDetail -InputObject $Object -TypeName 'ApplicationX.Account' -PropertyToAdd @{ AnotherProperty = 5 } # First Last Account AnotherProperty # ----- ---- ------- --------------- # Cookie Monster CMonster 5 #Verify that get-member shows us the right type $Object | Get-Member # TypeName: ApplicationX.Account ... .EXAMPLE # # Create an object to work with $Object = [PSCustomObject]@{ First = 'Cookie' Last = 'Monster' Account = 'CMonster' } #Add a random property, set a default property set so we only see two props by default Add-ObjectDetail -InputObject $Object -PropertyToAdd @{ AnotherProperty = 5 } -DefaultProperties Account, AnotherProperty # Account AnotherProperty # ------- --------------- # CMonster 5 #Verify that the other properties are around $Object | Select -Property * # First Last Account AnotherProperty # ----- ---- ------- --------------- # Cookie Monster CMonster 5 .NOTES This breaks the 'do one thing' rule from certain perspectives... The goal is to decorate an object all in one shot This abstraction simplifies decorating an object, with a slight trade-off in performance. For example: 10,000 objects, add a property and typename: Add-ObjectDetail: ~4.6 seconds Add-Member + PSObject.TypeNames.Insert: ~3 seconds Initial code borrowed from Shay Levy: http://blogs.microsoft.co.il/scriptfanatic/2012/04/13/custom-objects-default-display-in-powershell-30/ .LINK http://ramblingcookiemonster.github.io/Decorating-Objects/ .FUNCTIONALITY PowerShell Language #> [CmdletBinding()] param( [Parameter( Mandatory = $true, Position = 0, ValueFromPipeline = $true )] [ValidateNotNullOrEmpty()] [psobject[]]$InputObject, [Parameter( Mandatory = $false, Position = 1)] [string]$TypeName, [Parameter( Mandatory = $false, Position = 2)] [System.Collections.Hashtable]$PropertyToAdd, [Parameter( Mandatory = $false, Position = 3)] [ValidateNotNullOrEmpty()] [Alias('dp')] [System.String[]]$DefaultProperties, [boolean]$Passthru = $True ) Begin { if ($PSBoundParameters.ContainsKey('DefaultProperties')) { # define a subset of properties $ddps = New-Object System.Management.Automation.PSPropertySet DefaultDisplayPropertySet, $DefaultProperties $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps } } Process { foreach ($Object in $InputObject) { switch ($PSBoundParameters.Keys) { 'PropertyToAdd' { foreach ($Key in $PropertyToAdd.Keys) { #Add some noteproperties. Slightly faster than Add-Member. $Object.PSObject.Properties.Add( ( New-Object System.Management.Automation.PSNoteProperty($Key, $PropertyToAdd[$Key]) ) ) } } 'TypeName' { #Add specified type [void]$Object.PSObject.TypeNames.Insert(0, $TypeName) } 'DefaultProperties' { # Attach default display property set Add-Member -InputObject $Object -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers } } if ($Passthru) { $Object } } } } function ConvertTo-RundeckDuration { [CmdletBinding()] [OutputType( [System.String] )] param ( [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.DateTime] $Date ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" $CurrentDate = Get-Date } process { $duration = $Date - $CurrentDate if( $duration.Days -gt '1' ) { return "$($duration.Days)d" } elseif($duration.Hours -gt '1' ) { return "$($duration.Hours)h" } elseif($duration.Minutes -gt '1' ) { return "$($duration.Minutes)m" } else { return "$($duration.Seconds)s" } } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckApiData { <# .SYNOPSIS Retrieves data for making requests to the Rundeck API. .DESCRIPTION Retrieves the data necessary to construct an API request based on the specified cmdlet name. .INPUTS System.String .INPUTS System.Management.Automation.PSObject .EXAMPLE Get-ArmorApiData -FunctionName 'Connect-Rundeck' Retrieves the data necessary to construct a request for `Connect-Rundeck` #> [CmdletBinding()] param ( # Specifies the cmdlet name to lookup the API data for. [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullorEmpty()] [System.String] $FunctionName ) begin { $function = $MyInvocation.MyCommand.Name Write-Verbose -Message "Beginning: '${function}' with ParameterSetName '$( $PSCmdlet.ParameterSetName )' and Parameters: $( $PSBoundParameters | Out-String )" } process { Write-Verbose -Message "[$($MyInvocation.MyCommand.Name)] Gather API Data for: '${FunctionName}'." $filePath = Join-Path -Path $script:ModulePath -ChildPath 'Config/ApiData.json' $api = Get-Content -Path $filePath | ConvertFrom-Json -ErrorAction 'Stop' if ( $null -eq ( $api | Get-Member -Name $FunctionName -ErrorAction 'SilentlyContinue' ) ) { throw "Invalid endpoint: '${FunctionName}'" } else { return $api.$FunctionName } } end { Write-Verbose -Message "Ending: '${function}'." } } function Invoke-RundeckRestMethod { <# .SYNOPSIS Sends data to an Rundeck API endpoint and then formats the response for further use. .DESCRIPTION Sends HTTPS requests to a web page or web service via Invoke-WebRequest. If the expected HTTP response code is received, the response content is converted from JSON and passed to the pipeline; otherwise, the HTTP response code description is thrown as a terminating error. .EXAMPLE Invoke-RundeckRestMethod -Uri https://rundeck.local/api/19/tokens/ -Method Get -SuccessCode 200 Submits a GET request to the Rundeck API endpoint during a valid session, converts the JSON response body to an object, passes the object to the pipeline, and then outputs the object. #> [CmdletBinding()] [OutputType( [PSCustomObject[]] )] [OutputType( [PSCustomObject] )] param ( <# Specifies the Uniform Resource Identifier (URI) of the Armor API resource to which the web request is sent. #> [Parameter( Mandatory = $true, Position = 0 )] #[ValidateScript( { $_ -match '^(http|https)://.+/.+$/g' } )] [System.String] $Uri, # Specifies the headers of the Rundeck API web request. [Parameter( Position = 1 )] [ValidateNotNull()] [Hashtable] $Headers = $Script:rundeckSession.Headers, # Specifies the action/method used for the Rundeck API web request. [Parameter( Mandatory = $true, Position = 2 )] [ValidateSet( 'Delete', 'Get', 'Patch', 'Post', 'Put' )] [System.String] $Method = 'Get', <# Specifies the body of the Armor API request. Ignored if the request method is set to Get. #> [Parameter( Position = 3 )] [AllowEmptyString()] [System.String] $Body = '', # Specifies the success code expected in the response. [Parameter( Mandatory = $true, Position = 4 )] [ValidateSet( 200, 201, 202, 204 )] [System.UInt16] $SuccessCode ) begin { $function = $MyInvocation.MyCommand.Name Write-Verbose -Message "Beginning: '${function}' with ParameterSetName '$( $PSCmdlet.ParameterSetName )' and Parameters: $( $PSBoundParameters | Out-String )" } process { [PSCustomObject[]] $return = $null $request = $null Write-Verbose -Message "Submitting the request: $( $Method.ToUpper() ) ${Uri}" if ( $Method -eq 'Get' ) { $getHeaders = $Headers.Clone() $getHeaders.Remove( 'Content-Type' ) $request = Invoke-WebRequest -Uri $Uri -Headers $getHeaders -Method $Method -Verbose } else { Write-Verbose ($Headers | Out-String ) Write-Verbose ($Body | Out-String ) $request = Invoke-WebRequest -Uri $Uri -Headers $Headers -Method $Method -Body $Body -ContentType 'application/json' } if ( $request.StatusCode -eq $SuccessCode ) { $return = $request.Content | ConvertFrom-Json } else { throw $request.StatusDescription } $return } end { Write-Verbose -Message "Ending: '${function}'." } } function New-RundeckApiUri { <# .SYNOPSIS Builds the Armor API URI with the endpoint. .DESCRIPTION Builds the Armor API URI with the appropriate endpoint for the number of IDs specified. .EXAMPLE New-ArmorApiUri -Server 'api.armor.com' -Port 443 -Endpoints '/auth/authorize' This will return 'https://api.armor.com:443/auth/authorize'. .EXAMPLE New-ArmorApiUri -Server 'api.armor.com' -Port 443 -Endpoints '/vms', '/vms/{id}' -IDs 1 This will return 'https://api.armor.com:443/vms/1'. .EXAMPLE New-ArmorApiUri -Server 'api.armor.com' -Port 443 -Endpoint '/apps/{id}/tiers', '/apps/{id}/tiers/{id}' -IDs 1, 2 This will return 'https://api.armor.com:443/apps/1/tiers/2'. #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'Low' )] [OutputType( [String] )] param ( # Specifies the Armor API server protocol. [Parameter( Position = 0 )] [ValidateSet('http', 'https')] [System.String] $Protocol = $Script:RundeckSession.Protocol, # Specifies the Armor API server IP address or FQDN. [Parameter( Position = 1 )] [ValidateNotNullOrEmpty()] [String] $Server = $Script:RundeckSession.Server, # Specifies the Armor API server port. [Parameter( Position = 2 )] [ValidateRange( 1, 65535 )] [UInt16] $Port = $Script:RundeckSession.Port, # Specifies the array of available endpoint paths. [Parameter( Mandatory = $true, Position = 3 )] [ValidateScript( { $_ -match '^/' } )] [String[]] $Endpoint, # Specifies the positional ID values to be inserted into the path. [Parameter( Position = 4 )] [ValidateCount( 0, 2 )] [AllowEmptyCollection()] [String[]] $Id ) begin { $function = $MyInvocation.MyCommand.Name Write-Verbose -Message "Beginning: '${function}' with ParameterSetName '$( $PSCmdlet.ParameterSetName )' and Parameters: $( $PSBoundParameters | Out-String )" } process { [String] $return = $null if ( $PSCmdlet.ShouldProcess( 'Build the Rundeck API URI' ) ) { Write-Verbose -Message 'Build the URI.' switch ( ( $ID | Measure-Object ).Count ) { 0 { $endpoint = $Endpoint.Where( { $_ -notmatch '{id}' } ) if ( ( $endpoint | Measure-Object ).Count -eq 0 ) { throw 'Endpoint with no ID specification not found.' } elseif ( ( $endpoint | Measure-Object ).Count -ne 1 ) { throw 'More than one endpoint with no ID specification found.' } else { $endpoint = $endpoint[0] } $return = "${Protocol}://${Server}:${Port}${endpoint}" } 1 { $endpoint = $Endpoint.Where( { $_ -match '/{id}' -and $_ -notmatch '/{id}.*/{id}' } ) if ( ( $endpoint | Measure-Object ).Count -eq 0 ) { throw 'Endpoint with one ID specification not found.' } elseif ( ( $endpoint | Measure-Object ).Count -ne 1 ) { throw 'More than one endpoint with one ID specification found.' } else { $endpoint = $endpoint[0] } $return = "${Protocol}://${Server}:${Port}${endpoint}" # Insert ID in URI string $return = $return -replace '{id}', $ID[0] } 2 { $endpoint = $Endpoint.Where( { $_ -match '/{id}.*/{id}' -and $_ -notmatch '/{id}.*/{id}.*/{id}' } ) if ( ( $endpoint | Measure-Object ).Count -eq 0 ) { throw 'Endpoint with two ID specifications not found.' } elseif ( ( $endpoint | Measure-Object ).Count -ne 1 ) { throw 'More than one endpoint with two ID specifications found.' } else { $endpoint = $endpoint[0] } $return = "${Protocol}://${Server}:${Port}${endpoint}" # Insert first ID in URI string $return = $return -replace '(.*?)/{id}(.*)', "`$1/$( $ID[0] )`$2" # Insert second ID in URI string $return = $return -replace '{id}', $ID[1] } } Write-Verbose -Message "URI = ${return}" } $return } end { Write-Verbose -Message "Ending: '${function}'." } } function New-RundeckAuthToken { param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [System.String]$Username, [Parameter( Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange( 1, 60 )] [System.UInt16] $Duration, [Parameter( Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [System.String[]]$Roles, [Parameter( Position = 2, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] $Session ) begin { $function = $MyInvocation.MyCommand.Name Write-Verbose "[$function] Function started with ParameterSetName '$( $PSCmdlet.ParameterSetName )' and Parameters: $( $PSBoundParameters | Out-String )" } process { $resources = Get-RundeckApiData -FunctionName $function $uri = New-RundeckApiUri -Endpoint $resources.Endpoints $body = @{ $resources.Body.Username = $UserName $resources.Body.Roles = $Roles $resources.Body.Duration = "${Duration}m" } | ConvertTo-Json $requestData = @{ Uri = $uri Method = $resources.Method ContentType = 'application/json' Body = $body WebSession = $Session Headers = @{ 'accept' = 'application/json' } } $request = Invoke-WebRequest @requestData return ($request.Content | convertfrom-json).token } end { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete." } } function Test-RundeckSession { <# .SYNOPSIS Tests the validity of the Armor API session. .DESCRIPTION Test to see if a session has been established with the Rundeck API and that it has not yet expired. If no token is found, an error will be thrown. If the session has expired, Disconnect-Rundeck will be called with confirmation disabled to clean up the session. If less than 2/3 of the session length remain, Update-RundeckApiToken will be called to renew the session. This cmdlet should be called in the Begin section of public cmdlets for optimal performance, so that the session is not tested repeatedly when pipeline input is processed. .EXAMPLE Test-RundeckSession Validates that the Armor API session stored in $Script:RundeckSession is still active. #> [CmdletBinding()] [OutputType( [Void] )] param () begin { $function = $MyInvocation.MyCommand.Name Write-Verbose -Message "Beginning: '${function}' with ParameterSetName '$( $PSCmdlet.ParameterSetName )' and Parameters: $( $PSBoundParameters | Out-String )" } process { Write-Verbose -Message 'Verify that the session authorization exists.' if ( -not $Script:RundeckSession ) { throw 'Session not found. Please log in again.' } elseif ( -not $Script:RundeckSession.AuthorizationExists() ) { throw 'Session authorization not found. Please log in again.' } Write-Verbose -Message 'Verify that the session is active.' if ( $Script:RundeckSession.IsActive() ) { $minutesRemaining = $Script:RundeckSession.GetMinutesRemaining() Write-Verbose -Message "${minutesRemaining} minutes remaining until session expiration." if ( $minutesRemaining -lt ( $Script:RundeckSession.SessionLengthInMinutes * ( 2 / 3 ) ) ) { Write-Verbose -Message 'Renewing session token.' Update-RundeckAuthToken -Token $Script:RundeckSession.GetToken() } } else { $expirationTime = $Script:RundeckSession.SessionExpirationTime Disconnect-Rundeck -Confirm:$false throw "Session expired at ${expirationTime}. Please log in again." } } end { Write-Verbose -Message "Ending: '${function}'." } } # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # + Public Function # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ function Get-RundeckExecutionOutput { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.UInt16] $Id, [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Node ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $resources = Get-RundeckApiData -FunctionName $functionName if ($Node) { $uri = New-RundeckApiUri -Endpoint $resources.Endpoints -Id $Id, $Node } else { $uri = New-RundeckApiUri -Endpoint $resources.Endpoints -Id $Id } $splat = @{ Uri = $uri Method = $resources.Method SuccessCode = $resources.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckExecutionStatus { param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.UInt16] $Id ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $resources = Get-RundeckApiData -FunctionName $functionName $uri = New-RundeckApiUri -Endpoint $resources.Endpoints -Id $Id $splat = @{ Uri = $uri Method = $resources.Method SuccessCode = $resources.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckProjectJob { [CmdletBinding()] [OutputType( [System.Object] )] Param ( # Specify the project name [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Project, # Specify a comma-separated list of Job IDs to include [Parameter( Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String[]] $Idlist, # Specify a group or partial group path to include all jobs within that group path. (Default value: "*", all groups). # Set to the special value "-" to match the top level jobs only [Parameter( Position = 2, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $GroupPath = "*", # Specify a filter for the job Name. Matches any job name that contains this value. [Parameter( Position = 3, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $JobExactFilter, # Specify an exact group path to match. Set to the special value “-” to match the top level jobs only [Parameter( Position = 4, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $GroupPathExact = "-", # Specify whether to return only scheduled or only not scheduled jobs. [Parameter( Position = 5, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.Boolean] $ScheduledFilter, # Value: a UUID. In cluster mode, use to select scheduled jobs assigned to the server with given UUID. [Parameter( Position = 6, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $serverNodeUUIDFilter ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) $splat = @{ Uri = $apiData.GetEndpointUri($Project) Body = $apiData.ConvertParameterToJsonBody($PSBoundParameters) Method = $apiData.Method SuccessCode = $apiData.SuccessCode ErrorAction = "Stop" } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Invoke-RundeckJob { [CmdletBinding()] param ( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Id, [Parameter( Position = 1, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateSet('DEBUG', 'VERBOSE', 'INFO', 'WARN', 'ERROR')] [System.String] $Loglevel = 'INFO', [Parameter( Position = 2, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $User, [Parameter( Position = 3, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Filter, [Parameter( Position = 4, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.DateTime] $RunAtTime ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $resources = Get-RundeckApiData -FunctionName $functionName $uri = New-RundeckApiUri -Endpoint $resources.Endpoints -Id $Id $body = @{ $resources.Body.Loglevel = $Loglevel $resources.Body.User = $User $resources.Body.Filter = $Filter } if ($RunAtTime) { $body.($resources.Body.RunAtTime) = [System.String]$RunAtTime.ToString("yyyy-MM-dd\THH:mm:ss.fff-000") } Write-Debug ($body | Out-String) $splat = @{ Uri = $uri Body = ($body | ConvertTo-Json ) Method = $resources.Method SuccessCode = $resources.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckProject { [CmdletBinding()] Param( [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Name ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) if ($Name) { $uri = $apiData.GetEndpointUri($Name) } else { $uri = $apiData.GetEndpointUri() } $splat = @{ Uri = $uri Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function New-RundeckProject { [CmdletBinding()] Param( # [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Name, [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String[]] $Config ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) $body = @{ $apiData.Body.Name = $Name $apiData.Body.Config = $Config } $splat = @{ Uri = $apiData.GetEndpointUri() Body = ( $body | ConvertTo-Json ) Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Remove-RundeckProject { [CmdletBinding()] [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] Param( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Name ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { if ( $PSCmdlet.ShouldProcess( 'Rundeck Project', 'Remove' ) ) { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) $splat = @{ Uri = $apiData.GetEndpointUri($Name) Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } } end { Write-Verbose "[${functionName}] Complete" } } function Connect-Rundeck { <# .SYNOPSIS Connects to the Rundeck API and establishes a session. .EXAMPLE Connect-Rundeck Prompts for the username and password, and then attempts to log into the Rundeck API. .EXAMPLE Connect-Rundeck -Credential $pscredential Attempts to log into the Rundeck API with the credentials stored in the $pscredential object. .EXAMPLE Connect-Rundeck -Credential $pscredential -Server 'localhost' -Port 8443 Attempts to log into the Rundeck API on port 8443/tcp with the credentials stored in the $pscredential object. .EXAMPLE Connect-Rundeck -Token 'kv9J9MjNYIf9KaIK2GDm5XtW3diS3C5R' Attempts to log into the Rundeck API with the user token. #> [CmdletBinding( DefaultParameterSetName = 'Credential' )] [OutputType( [RundeckSession] )] Param( # Specifies the Rundeck API protocol. [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateSet('http', 'https')] [System.String] $Protocol = 'https', # Specifies the Rundeck API server IP address or FQDN. [Parameter( Position = 1, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [System.String] $Server, # Specifies the rundeck API server listening TCP port. [Parameter( Position = 2, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateRange( 1, 65535 )] [System.UInt16] $Port = 443, # Specifies the user credential. [Parameter( Position = 3, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Credential' )] [ValidateNotNullOrEmpty()] [System.Management.Automation.PSCredential] $Credential, # Specifies the role used to create a temporary token [Parameter( Position = 4, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Credential' )] [ValidateNotNullOrEmpty()] [System.String[]] $Roles = '*', # Specifies session duration. [Parameter( Position = 5, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Credential' )] [ValidateRange( 1, 30 )] [System.UInt16] $Duration = 30, # Specifies the Rundeck API token. [Parameter( Position = 6, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Token' )] [ValidateNotNullOrEmpty()] [System.String] $Token ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" } process { Write-Verbose -Message '[$functionName] Storing all session details in $Script:rundeckSession.' [RundeckSession] $Script:RundeckSession = [RundeckSession]::New( $Protocol, $Server, $Port ) if ( $PSCmdlet.ParameterSetName -eq 'Credential' ) { $resources = Get-RundeckApiData -FunctionName $functionName $uri = New-RundeckApiUri -Endpoint $resources.Endpoints $body = @{ $resources.Body.Username = $Credential.UserName $resources.Body.Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.Password)) } $sessionRequestData = @{ Method = $resources.Method Uri = $uri body = $body UseBasicParsing = $true ContentType = "application/x-www-form-urlencoded" SessionVariable = 'temporarySession' } $sessionRequest = Invoke-WebRequest @sessionRequestData if ($sessionRequest.BaseResponse.ResponseUri.AbsolutePath -eq '/menu/home') { Write-Verbose -Message "[${functionName}] Successfully acquired temporary session" $splats = @{ Username = $Credential.UserName Session = $temporarySession Roles = $Roles Duration = $Duration } $Token = New-RundeckAuthToken @splats $Script:RundeckSession.Authorize( $Token, $Duration ) # Return RundeckSession object return $Script:RundeckSession } else { throw 'Failed to obtain temporary session, please check your credential.' } } else { throw 'Token feature is not implemented' } } end { Write-Verbose "[${functionName}] Complete" } } function Disconnect-Rundeck { <# .SYNOPSIS Disconnects from Rundeck and destroys the session information. .DESCRIPTION Disconnects from the Rundeck API and destroys the $Script:RundeckSession session variable. .EXAMPLE Disconnect-Rundeck Disconnects from the Rundeck API and destroys the $Script:RundeckSession session variable. #> [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] [OutputType( [System.Void] )] param () begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" } process { if ( $PSCmdlet.ShouldProcess( 'Rundeck session', 'Disconnect' ) ) { Write-Verbose -Message "[${functionName}] Disconnecting from Rundeck." Set-Variable -Scope 'Script' -Name 'RundeckSession' -Value $null -Force Remove-Variable -Scope 'Script' -Name 'RundeckSession' -Force } } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckSystemInfo { [CmdletBinding()] Param() begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) $splat = @{ Uri = $apiData.GetEndpointUri() Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckToken { <# .SYNOPSIS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .DESCRIPTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .EXAMPLE PS C:\> Get-RundeckToken .OUTPUTS System.Object .NOTES - File Name : Get-RundeckToken.ps1 - Author : Thomas ILLIET #> [CmdletBinding()] [OutputType( [System.Object] )] Param() begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) $splat = @{ Uri = $apiData.GetEndpointUri() Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function New-RundeckToken { <# .SYNOPSIS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .DESCRIPTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .PARAMETER Username XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .PARAMETER Roles XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .PARAMETER Duration XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .EXAMPLE PS C:\> New-RundeckToken -Username illietth -Duration ( (Get-Date).addminutes(30) ) .EXAMPLE PS C:\> New-RundeckToken -Username illietth -Roles '*' -Duration ( (Get-Date).addminutes(30) ) .INPUTS System.String .INPUTS System.Management.Automation.PSObject .OUTPUTS System.Object .NOTES - File Name : New-RundeckToken.ps1 - Author : Thomas ILLIET #> [CmdletBinding()] Param( [Parameter( Position = 0, Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Username, [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [ValidateNotNullOrEmpty()] [System.String[]] $Roles = '*', [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.DateTime] $Duration ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $resources = Get-RundeckApiData -FunctionName $functionName $uri = New-RundeckApiUri -Endpoint $resources.Endpoints $body = @{ $resources.Body.Username = $Username $resources.Body.Roles = $Roles $resources.Body.Duration = ( ConvertTo-RundeckDuration -Date $Duration ) } $splat = @{ Uri = $uri Body = ( $body | ConvertTo-Json ) Method = $resources.Method SuccessCode = $resources.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } function Remove-RundeckToken { <# .SYNOPSIS XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .DESCRIPTION XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .PARAMETER Id XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX .EXAMPLE PS C:\> New-HCoreCache -Name "MyCache" -InputObject ([PSCustomObject]@{ test = "test" }) .INPUTS System.String .INPUTS System.Management.Automation.PSObject .OUTPUTS System.Object .LINK https://hardening.netboot.fr/configuration/powershell/private/new-hcorecache/ .LINK https://github.com/HardeningPS/HardeningCore/blob/stable/HardeningCore/Private/New-HCoreCache.ps1 .NOTES - File Name : Remove-RundeckToken.ps1 - Author : Thomas ILLIET #> [CmdletBinding()] [CmdletBinding( SupportsShouldProcess = $true, ConfirmImpact = 'High' )] [OutputType( [System.Object] )] Param( [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Id ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { if ( $PSCmdlet.ShouldProcess( 'Rundeck Token', 'Remove' ) ) { $resources = Get-RundeckApiData -FunctionName $functionName $uri = New-RundeckApiUri -Endpoint $resources.Endpoints -Id $Id $splat = @{ Uri = $uri Method = $resources.Method SuccessCode = $resources.SuccessCode } return (Invoke-RundeckRestMethod @splat) } } end { Write-Verbose "[${functionName}] Complete" } } function Get-RundeckUser { [CmdletBinding()] Param( [Parameter( Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true )] [System.String] $Name ) begin { $functionName = $MyInvocation.MyCommand.Name Write-Verbose "[${functionName}] Function started" Write-Verbose "[${functionName}] ParameterSetName '$( $PSCmdlet.ParameterSetName )'" Write-Verbose "[${functionName}] Parameters: $( $PSBoundParameters | Out-String )'" Test-RundeckSession } process { $apiData = [RundeckApiData]::new($script:RundeckSession, $functionName) if ($Name) { $uri = $apiData.GetEndpointUri($Name) } else { $uri = $apiData.GetEndpointUri() } $splat = @{ Uri = $uri Method = $apiData.Method SuccessCode = $apiData.SuccessCode } return (Invoke-RundeckRestMethod @splat) } end { Write-Verbose "[${functionName}] Complete" } } |