PSPuppetOrchestrator.psm1
function Set-ServerCertificateValidationCallback { <# .SYNOPSIS A stub for testing purposes. .DESCRIPTION This cmdlet stubs the call to setting the callback to true so that it can tested via pester and it's use can be detected via Assert-MockCalled .EXAMPLE Set-ServerCertificateValidationCallback This is a stub method used internally to make Pester Testing easier. It sets the ServerCertificateValidationCallback method in a way that's easier to Mock during testing. #> [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} } function Set-SkipCertificateCheck { <# .SYNOPSIS Detect Invoke-RestMethod version and ignore server ssl certs. .DESCRIPTION Detect whether certificate checking should be skipped via the switch parameter on Invoke-RestMethod or via setting the ServerCertificateCallback setting. .PARAMETER SkipCertificateCheck Indicate that you would like to skip SSL Certificate validation from the Master server and trust the certificate you receive. .PARAMETER PARAMS The hash table of params you will eventually use with Invoke-RestMethod. This cmdlet will add a SkipCertificateCheck key and set it to true if the current version of Invoke-RestMethod supports that switch. If it does not then it will set the ServerCertificateValidationCallback method to always return true and achieve the same effect. .EXAMPLE $invokeParams = @{ >> Uri = $uri >> Method = 'Post' >> Body = $req >> ContentType = 'application/json' >> } PS > $invokeParams = Set-SkipCertificateCheck -SkipCertificateCheck $SkipCertificateCheck -Params $invokeParams PS > Invoke-RestMethod @invokeParams | Select-Object -ExpandProperty token This example detect if the current version of Invoke-RestMethod implements the -SkipCertificateCheck switch parameter If it does then it will add that key to the $invokeParams hash table for use when splatting with Invoke-RestMethod. If it does not then it will set the ServerCertificateValidationCallback method in .NET to always return $true to acheive the same effect in PowerShell 5.1 #> [CmdletBinding()] param ( [Parameter(Mandatory)] [bool] $SkipCertificateCheck, [Parameter(Mandatory)] [hashtable] $params ) process { if ($SkipCertificateCheck) { if (Get-Help Invoke-RestMethod -Parameter SkipCertificateCheck -ErrorAction SilentlyContinue) { $params.SkipCertificateCheck = $true } else { Set-ServerCertificateValidationCallback } } $params } } Function Wait-PuppetNodePCPBroker { <# .SYNOPSIS Returns Hello world .DESCRIPTION Wait-PuppetNodePCPBroker was originally written in an effort to detect when nodes rebooted by evaluating a nodes's PCP Broker connected state. Since the advent of the reboot plan as seen in https://github.com/puppetlabs/puppetlabs-reboot/blob/master/plans/init.pp Wait-PuppetNodePCPBroker is no longer a viable solution. Never was to begin with really. .PARAMETER Timeout x .PARAMETER Token x .PARAMETER Master x .PARAMETER Node x .EXAMPLE PS> Get-HelloWorld Runs the command #> Param( [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master, [Parameter(Mandatory)] [string]$Node, [Parameter()] [int]$Timeout = 300 ) $detailsSplat = @{ token = $Token master = $master node = $node } # create a timespan $timespan = New-TimeSpan -Seconds $timeout # start a timer $stopwatch = [diagnostics.stopwatch]::StartNew() # get the broker status every 5 seconds until our timeout is met while ($stopwatch.elapsed -lt $timespan) { # get the broker status if (($one = Get-PuppetNodePCPBrokerDetails @detailsSplat).connected -eq $false) { # broker status is disconnected, sleep 5s and check again to confirm not a blip or false positive Write-Verbose "Broker status is $($one.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." Write-Verbose "Sleping 5 seconds and checking again." Start-Sleep -Seconds 5 if (($two = Get-PuppetNodePCPBrokerDetails @detailsSplat).connected -eq $false) { Write-Verbose "Broker status is still $($two.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." # broker status is disconnected, break out of the loop break } } else { Write-Verbose "Broker status is $($one.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." } Start-Sleep -Seconds 5 } if ($stopwatch.elapsed -ge $timespan) { Write-Error "Timeout of $Timeout`s has exceeded." break } Write-Verbose "$Node broker status confirmed disconnected." # get the broker status every 5 seconds until our timeout is met while ($stopwatch.elapsed -lt $timespan) { # get the broker status if (($three = Get-PuppetNodePCPBrokerDetails @detailsSplat).connected -eq $true) { # broker status is connected, sleep 5s and check again to confirm not a blip or false positive Write-Verbose "Broker status is $($three.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." Write-Verbose "Sleping 5 seconds and checking again." Start-Sleep -Seconds 5 if (($four = Get-PuppetNodePCPBrokerDetails @detailsSplat).connected -eq $true) { Write-Verbose "Broker status is still $($four.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." # broker status is connected, break out of the loop break } } else { Write-Verbose "Broker status is $($three.connected), (timeout: $($stopwatch.elapsed.TotalSeconds)s of $Timeout`s elapsed)." } Start-Sleep -Seconds 5 } if ($stopwatch.elapsed -ge $timespan) { Write-Error "Timeout of $Timeout`s has exceeded." break } Write-Verbose "$Node broker status confirmed connected." } function Get-PuppetAuthToken { <# .SYNOPSIS Get a Puppet Auth Token for use with API calls. .DESCRIPTION Call the /rbac-api/v1/auth/token end point with a username and password to obtain an authorization token that can be used with the other cmdlets in this module. .PARAMETER Master The FQDN of the Puppet Master server that your DNS can resolve. You do not need to include the HTTPS portion of the address. Only the server name. .PARAMETER Lifetime An integer value specifying how long you would like the token to last paired with one of the letter [smhdy] (example '2d' for two days) to specify the units of time in seconds, minutes, hours, days, or years. If you call this cmdlet to request a token within the valid lifetime of your current token, you will receive your current token back again. .PARAMETER Port The port your Puppet Master API end poins are listening on. Be default this will be 4433, but it is configurable. .PARAMETER Credential A PSCredential object that contains the username and password for the user you would like to receive a token for. If you do not provide one you will be prompted at the commandline for one. .PARAMETER SkipCertificateCheck Skip certificate validation on the SSL certificate provided by the Puppet master server you connect to. This parameter will function as expected for both PowerShell 5.1 and 6+. .EXAMPLE $cred = Get-Credential -Username Admin PS > $token = Get-PuppetAuthToken -Master 'pe-master.corp.net' -SkipCertificateCheck -Credential $cred Create a credential object and then call this cmdlet to retrieve a token .EXAMPLE $token = Get-PuppetAuthToken -Master 'pe-master.corp.net' -SkipCertificateCheck If you do not pass a credential object, you will be prompted for credentials. .EXAMPLE $token = Get-PuppetAuthToken -Lifetime '2m' -Master 'pe-master.corp.net' -SkipCertificateCheck Create a short lived token so that an automated process can use it but it dies quickley thereafter. .INPUTS Inputs (if any) .OUTPUTS [String] Authorization Token Value. #> [CmdletBinding()] param ( [Parameter(Mandatory)] [String]$Master, [Parameter()] [String] $Lifetime = '1d', [Parameter()] [Int]$Port = 4433, [Parameter(Mandatory)] [PSCredential]$Credential, [Parameter()] [Switch]$SkipCertificateCheck = $false ) process { $uri = "https://$Master`:$port/rbac-api/v1/auth/token" $req = [PSCustomObject]@{ login = $credential.UserName password = $credential.GetNetworkCredential().Password lifetime = $Lifetime description = "Token used for authentication to Puppet api requests." label = "Personal Workstation token" } | ConvertTo-JSON $invokeParams = @{ Uri = $uri Method = 'Post' Body = $req ContentType = 'application/json' } $invokeParams = Set-SkipCertificateCheck -SkipCertificateCheck $SkipCertificateCheck -Params $invokeParams try { Invoke-RestMethod @invokeParams | Select-Object -ExpandProperty token } catch { Write-Error $_.Exception.Message } finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $null } } } Function Get-PuppetJob { <# .SYNOPSIS Get details on a Puppet job. .DESCRIPTION Get details on a Puppet job. .PARAMETER ID The ID of the job. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetJob -Token $token -Master $master -ID 906 description : report : @{id=https://puppet:8143/orchestrator/v1/jobs/906/report} name : 906 events : @{id=https://puppet:8143/orchestrator/v1/jobs/906/events} command : task type : task state : failed nodes : @{id=https://puppet:8143/orchestrator/v1/jobs/906/nodes} status : {@{state=ready; enter_time=2019-09-04T16:50:09Z; exit_time=2019-09-04T16:50:10Z}, @{state=running; enter_time=2019-09-04T16:50:10Z; exit_time=2019-09-04T16:50:43Z}, @{state=failed; enter_time=2019-09-04T16:50:43Z; exit_time=}} id : https://puppet:8143/orchestrator/v1/jobs/906 environment : @{name=production} options : @{description=; transport=pxp; noop=False; task=powershell_tasks::getkb; sensitive=System.Object[]; params=; scope=; environment=production} timestamp : 2019-09-04T16:50:43Z owner : @{email=; is_revoked=False; last_login=2019-09-04T16:48:50.049Z; is_remote=False; login=admin; is_superuser=True; id=42bf351c-f9ec-40af-84ad-e976fec7f4bd; role_ids=System.Object[]; display_name=Administrator; is_group=False} node_count : 3 node_states : @{failed=1; finished=2} #> Param( [Parameter(Mandatory)] [int]$ID, [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master ) $hoststr = "https://$master`:8143/orchestrator/v1/jobs/$id" $headers = @{'X-Authentication' = $Token} $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers $content = $result Write-Output $content } Function Get-PuppetJobReport { <# .SYNOPSIS Get the report for a given Puppet job. .DESCRIPTION Get the report for a given Puppet job. .PARAMETER ID The ID of the job. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetJobReport -Master $master -Token $token -ID 906 node state start_timestamp finish_timestamp timestamp events ---- ----- --------------- ---------------- --------- ------ den3w108r2psv2 failed 2019-09-04T16:50:10Z 2019-09-04T16:50:12Z 2019-09-04T16:50:12Z {} den3w108r2psv3 finished 2019-09-04T16:50:10Z 2019-09-04T16:50:42Z 2019-09-04T16:50:42Z {} den3w108r2psv4 finished 2019-09-04T16:50:10Z 2019-09-04T16:50:43Z 2019-09-04T16:50:43Z {} #> Param( [Parameter(Mandatory)] [int]$ID, [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master ) $hoststr = "https://$master`:8143/orchestrator/v1/jobs/$id/report" $headers = @{'X-Authentication' = $Token} $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers foreach ($report in $result.report) { Write-Output $report } } Function Get-PuppetJobResults { <# .SYNOPSIS Get the results from a Puppet job. .DESCRIPTION Get the results from a Puppet job. .PARAMETER ID The ID of the job. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetJobResults -Master $master -Token $token -ID 930 finish_timestamp : 10/4/19 3:45:26 PM transaction_uuid : start_timestamp : 10/4/19 3:45:18 PM name : den3w108r2psv5 duration : 7.767 state : finished details : result : @{Source=DEN3W108R2PSV5; HotFixID=KB2620704; Description=Security Update; InstalledBy=NT AUTHORITY\SYSTEM; InstalledOn=Thursday, September 06, 2018 12:00:00 AM} latest-event-id : 5709 timestamp : 10/4/19 3:45:26 PM finish_timestamp : 10/4/19 3:45:34 PM transaction_uuid : start_timestamp : 10/4/19 3:45:19 PM name : den3w108r2psv3 duration : 15.264 state : finished details : result : @{Source=DEN3W108R2PSV3; HotFixID=KB2620704; Description=Security Update; InstalledBy=; InstalledOn=Friday, September 07, 2018 12:00:00 AM} latest-event-id : 5712 timestamp : 10/4/19 3:45:34 PM finish_timestamp : 10/4/19 3:45:34 PM transaction_uuid : start_timestamp : 10/4/19 3:45:19 PM name : den3w108r2psv4 duration : 15.505 state : finished details : result : @{Source=DEN3W108R2PSV4; HotFixID=KB2620704; Description=Security Update; InstalledBy=; InstalledOn=Friday, September 07, 2018 12:00:00 AM} latest-event-id : 5713 timestamp : 10/4/19 3:45:34 PM #> Param( [Parameter(Mandatory)] [int]$ID, [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master ) $hoststr = "https://$master`:8143/orchestrator/v1/jobs/$id/nodes" $headers = @{'X-Authentication' = $Token} $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers foreach ($item in $result.items) { Write-Output $item } } Function Get-PuppetPCPNodeBrokerDetails { <# .SYNOPSIS Get a node's PCP broker details. .DESCRIPTION Get a node's PCP broker details. This is useful if you want to know the status of PCP before executing a task or plan. .PARAMETER Node The Puppet node name. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetPCPNodeBrokerDetails -Master $master -Token $token -Node 'den3w108r2psv3' name : den3w108r2psv3 connected : True broker : pcp://puppet/server timestamp : 10/2/19 2:01:53 AM #> Param( [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master, [Parameter(Mandatory)] [string]$Node ) $hoststr = "https://$master`:8143/orchestrator/v1/inventory/$node" $headers = @{'X-Authentication' = $Token} $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers Write-Output $result } Function Get-PuppetTask { <# .SYNOPSIS Get details on a Puppet task. .DESCRIPTION Get details on a Puppet task. .PARAMETER Module The module of the puppet task, if applicable. .PARAMETER Name The name of the Puppet task. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetTask -Master $master -Token $token -Name 'reboot' id : https://puppet:8143/orchestrator/v1/tasks/reboot/init name : reboot permitted : True metadata : @{description=Reboots a machine; implementations=System.Object[]; input_method=stdin; parameters=; supports_noop=False} files : {@{filename=init.rb; sha256=fb7e0e0de640b82844be931e59405de73e1e290c9540c204a6c79838a0e39fce; size_bytes=2556; uri=}, @{filename=nix.sh; sha256=dfb2ddfe17056c316d7260bcce853aabc5b18a266888f76b23314d0d4c8daee5; size_bytes=692; uri=}, @{filename=win.ps1; sha256=155f5ab7d63f1913ccf8f4f5563f1b2be2a49130a4787a8c48ff770cfe8e6415; size_bytes=785; uri=}} environment : @{name=production; code_id=} .EXAMPLE PS> Get-PuppetTask -Master $master -Token $token -Module 'powershell_tasks' -Name 'disablesmbv1' id : https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/disablesmbv1 name : powershell_tasks::disablesmbv1 permitted : True metadata : @{description=A task to test if SMBv1 is enabled and optionally disable it.; input_method=powershell; parameters=; puppet_task_version=1} files : {@{filename=disablesmbv1.ps1; sha256=c10f3ae37a6e2686c419ec955ee51f9894109ed073bf5c3b3280255b3785e0dc; size_bytes=3536; uri=}} environment : @{name=production; code_id=} #> Param( [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master, [Parameter()] [string]$Module, [Parameter(Mandatory)] [string]$Name ) $hoststr = "https://$master`:8143/orchestrator/v1/tasks/$Module/$Name" $headers = @{'X-Authentication' = $Token} # try and get the task in it's standard form $moduleName/$taskName try { $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers -ErrorAction SilentlyContinue } catch { # try and get the task again assuming it's built in with a default task name of 'init' (e.g. reboot/init) try { $hoststr = "https://$master`:8143/orchestrator/v1/tasks/$name/init" $result = Invoke-RestMethod -Uri $hoststr -Method Get -Headers $headers } catch { Write-Error $_.exception.message } } if ($result) { Write-Output $result } } Function Get-PuppetTasks { <# .SYNOPSIS Get a list of Puppet Tasks. .DESCRIPTION Get a list of Puppet Tasks. .PARAMETER Environment The environment to use. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE PS> Get-PuppetTasks -token $token -master $master id name permitted -- ---- --------- https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/getkb powershell_tasks::getkb True https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/account_audit powershell_tasks::account_audit True https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/switch powershell_tasks::switch True https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/ps1exec powershell_tasks::ps1exec True https://puppet:8143/orchestrator/v1/tasks/powershell_tasks/disablesmbv1 powershell_tasks::disablesmbv1 True #> Param( [Parameter(Mandatory)] [string]$token, [Parameter(Mandatory)] [string]$master, [Parameter()] [string]$environment='production' ) $uri = "https://$master`:8143/orchestrator/v1/tasks" $headers = @{'X-Authentication' = $Token} $body = @{'environment' = $environment} $result = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers -Body $body foreach ($item in $result.items) { Write-Output $item } } Function Invoke-PuppetTask { <# .SYNOPSIS Invoke a Puppet task. .DESCRIPTION Invoke a Puppet task. .PARAMETER Task The name of the Puppet task to invoke. .PARAMETER Environment The name of the Puppet task environment. .PARAMETER Parameters A hash of parameters to supply the Puppet task, e.g. $Parameters = @{tp1 = 'foo';tp2 = 'bar'; tp3 = $true}. .PARAMETER Description A description to submit along with the task. .PARAMETER Nodes An array of node names to target, e.g. $Scope = @('DEN3W108R2PSV5','DEN3W108R2PSV4','DEN3W108R2PSV3'). .PARAMETER Query A PuppetDB or PQL query to use to discover nodes. The target is built from the certname values collected at the top level of the query, e.g. '["from", "inventory", ["=", "facts.os.name", "windows"]]'. .PARAMETER Node_group A classifier node group ID. The ID must correspond to a node group that has defined rules. It is not sufficient for parent groups of the node group in question to define rules. The user must also have permissions to view the node group. Any nodes specified in the scope that the user does not have permissions to run the task on are excluded, e.g. 7a692b61-8087-4452-9cf8-58ed2acee2a0. .PARAMETER WaitLoopInterval An optional time in seconds that the wait feature will re-check the invoked task. DEFAULTS to 5s. .PARAMETER Wait An optional wait value in seconds that Invoke-PuppetTask will use to wait until the invoked task completes. If the wait time is exceeded Invoke-PuppetTask will return a warning. .PARAMETER Token The Puppet API orchestrator token. .PARAMETER Master The Puppet master. .EXAMPLE $invokePuppetTaskSplat = @{ Token = $token Master = $master Task = 'powershell_tasks::disablesmbv1' Environment = 'production' Parameters = @{action = 'set'; reboot = $true} Description = 'Disable smbv1 on 08r2 nodes.' Nodes = @('DEN3W108R2PSV5','DEN3W108R2PSV4','DEN3W108R2PSV3') } PS> Invoke-PuppetTask @invokePuppetTaskSplat id name -- ---- https://puppet.contoso.us:8143/orchestrator/v1/jobs/1318 1318 .EXAMPLE $invokePuppetTaskSplat = @{ Token = $token Master = $master Task = 'powershell_tasks::disablesmbv1' Environment = 'production' Parameters = @{action = 'set'; reboot = $true} Description = 'Disable smbv1 on 08r2 nodes.' Query = '["from", "inventory", ["=", "facts.os.name", "windows"]]' } PS> Invoke-PuppetTask @invokePuppetTaskSplat id name -- ---- https://puppet.contoso.us:8143/orchestrator/v1/jobs/1318 1318 .EXAMPLE $invokePuppetTaskSplat = @{ Token = $token Master = $master Task = 'powershell_tasks::disablesmbv1' Environment = 'production' Parameters = @{action = 'set'; reboot = $true} Description = 'Disable smbv1 on 08r2 nodes.' Node_group = '7a692b61-8087-4452-9cf8-58ed2acee2a0' } PS> Invoke-PuppetTask @invokePuppetTaskSplat id name -- ---- https://puppet.contoso.us:8143/orchestrator/v1/jobs/1318 1318 #> Param( [Parameter(Mandatory)] [string]$Token, [Parameter(Mandatory)] [string]$Master, [Parameter(Mandatory)] [string]$Task, [Parameter()] [string]$Environment = 'production', [Parameter()] [hashtable]$Parameters = @{}, [Parameter()] [string]$Description = '', [Parameter()] [int]$Wait, [Parameter()] [int]$WaitLoopInterval = 5, [Parameter(Mandatory, ParameterSetName = "nodes")] [string[]]$Nodes, [Parameter(Mandatory, ParameterSetName = "query")] [string]$Query, [Parameter(Mandatory, ParameterSetName = "node_group")] [string]$Node_group ) # set the scope type to the name of the oh so cleverly named parameter set $scopeType = $PSCmdlet.ParameterSetName # set the scope to the value of the single parameter of the choosen parameter set switch ($scopeType) { 'nodes' {$scope = $Nodes} 'query' {$scope = $Query} 'node_group' {$scope = $Node_group} } $req = [PSCustomObject]@{ environment = $Environment task = $Task params = $Parameters description = $Description scope = [PSCustomObject]@{ $scopeType = $scope } } | ConvertTo-Json $hoststr = "https://$master`:8143/orchestrator/v1/command/task" $headers = @{'X-Authentication' = $Token} $result = Invoke-RestMethod -Uri $hoststr -Method Post -Headers $headers -Body $req if ($PSBoundParameters.ContainsKey('wait')) { # sleep 5s for the job to register Start-Sleep -Seconds 5 $jobSplat = @{ token = $Token master = $master id = $result.job.name } # create a timespan $timespan = New-TimeSpan -Seconds $Wait # start a timer $stopwatch = [diagnostics.stopwatch]::StartNew() # get the job state every 5 seconds until our timeout is met while ($stopwatch.elapsed -lt $timespan) { # options are new, ready, running, stopping, stopped, finished, or failed $job = Get-PuppetJob @jobSplat Write-Verbose $job.node_states if (($job.State -eq 'stopped') -or ($job.State -eq 'finished') -or ($job.State -eq 'failed')) { Write-Output $job break } Start-Sleep -Seconds $WaitLoopInterval } if ($stopwatch.elapsed -ge $timespan) { Write-Warning "Timeout of $wait`s has exceeded. Job $($job.name) may still be running. Last job status: $($job.State)." break } } else { Write-Output $result.job } } |