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"
    }
}