PSPuppetOrchestrator.psm1

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-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 Scope
        An array of nodes the Puppet task will be invoked against, e.g. $Scope = @('DEN3W108R2PSV5','DEN3W108R2PSV4','DEN3W108R2PSV3').
    .PARAMETER ScopeType
        When executing tasks against the /command/task API endpoint you can either use
        a scope type of 'node' or 'query'. At this time, PSPuppetOrchestrator only
        supports a ScopeType of 'node' which is the DEFAULT and only allowed option for
        the ScopeType parameter.
    .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 WaitLoopInterval
        An optional time in seconds that the wait feature will re-check the invoked task.
        DEFAULTS to 5s.
    .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.'
            Scope = @('DEN3W108R2PSV5','DEN3W108R2PSV4','DEN3W108R2PSV3')
            ScopeType = 'nodes'
            Wait = 120
            WaitLoopInterval = 2
        }
        PS> Invoke-PuppetTask @invokePuppetTaskSplat
    #>


    Param(
        [Parameter(Mandatory)]
        [string]$Token,
        [Parameter(Mandatory)]
        [string]$Master,
        [Parameter(Mandatory)]
        [string]$Task,
        [Parameter()]
        [string]$Environment = 'production',
        [Parameter()]
        [hashtable]$Parameters = @{},
        [Parameter()]
        [string]$Description = '',
        [Parameter(Mandatory)]
        [string[]]$Scope,
        [Parameter()]
        [ValidateSet('nodes')]
        [string]$ScopeType = 'nodes',
        [Parameter()]
        [int]$Wait,
        [Parameter()]
        [int]$WaitLoopInterval = 5
    )

    $req = [PSCustomObject]@{
        environment = $Environment
        task        = $Task
        params      = $Parameters
        description = $Description
        scope       = [PSCustomObject]@{
            $ScopeType  = $Scope
        }
    } | ConvertTo-Json
    $req
    $hoststr = "https://$master`:8143/orchestrator/v1/command/task"
    $headers = @{'X-Authentication' = $Token}

    $result  = Invoke-RestMethod -Uri $hoststr -Method Post -Headers $headers -Body $req
    $content = $result

    if ($wait) {
        # sleep 5s for the job to register
        Start-Sleep -Seconds 5

        $jobSplat = @{
            token  = $Token
            master = $master
            id     = $content.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 $content.job
    }
}