functions/functions.ps1


Function New-PSRemoteOperation {
    [cmdletbinding(DefaultParameterSetName = "scriptblock", SupportsShouldProcess)]
    [OutputType("None", [system.io.fileinfo])]
    [Alias('nro')]

    Param (
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = "Enter the name of the computer where this command will execute.")]
        [Alias("cn")]
        [ValidateNotNullorEmpty()]
        [string[]]$Computername,

        [Parameter(
            Mandatory,
            HelpMessage = "Enter a scriptblock to execute",
            ParameterSetName = "scriptblock"
        )]
        [alias("sb")]
        [ValidateNotNullorEmpty()]
        [scriptblock]$Scriptblock,

        [Parameter(
            Mandatory,
            HelpMessage = "Enter the path to the PowerShell script to execute. This is relative to the remote computer.",
            ParameterSetName = "filepath"
        )]
        [ValidateNotNullorEmpty()]
        [alias("sp")]
        [string]$ScriptPath,

        [Parameter(HelpMessage = "A hashtable of parameter names and values for your scriptblock or script.")]
        [Hashtable]$ArgumentList,

        [Parameter(HelpMessage = "A script block of commands to run prior to executing your script or scriptblock.")]
        [scriptblock]$Initialization,

        [ValidateScript( { Test-Path -Path $_ })]
        [Parameter(HelpMessage = "The folder where the remote operation file will be created.")]
        [string]$Path = $PSRemoteOpPath,

        [switch]$Passthru,

        [Parameter(HelpMessage = "Specify which version of PowerShell to use for the remote operation.")]
        [ValidateSet("Desktop", "Core")]
        [string]$PSVersion = "Desktop"

    )
    DynamicParam {
        if (Get-Command Protect-CmsMessage -ea silentlycontinue) {
            $attributes = New-Object System.Management.Automation.ParameterAttribute
            $attributes.HelpMessage = "Specify one or more CMS message recipients."

            $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
            $attributeCollection.Add($attributes)

            $dynParam1 = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("To", [System.Management.Automation.CmsMessageRecipient[]], $attributeCollection)
            $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
            $paramDictionary.Add("To", $dynParam1)
            return $paramDictionary
        }
    }

    Begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
    }

    Process {
        Write-Verbose "Using these PSBoundparameters"
        $PSBoundParameters | Out-String | Write-Verbose
        foreach ($Computer in $Computername) {
            Write-Verbose "Creating a remote operations file for $($computer.toUpper())"
            #define a here string for the psd1 content
            $Who = "$([System.Environment]::UserDomainName)\$([system.environment]::UserName)"

            $out = @"
@{
CreatedOn = '$([System.Environment]::Machinename)'
CreatedBy = '$($Who)'
CreatedAt = '$((Get-Date).toUniversalTime())'
Computername = '$($Computer.ToUpper())'
PSVersion = $PSVersion
Status = 'Pending'
 
"@


            if ($ArgumentList) {

                $opArgs = Convert-HashtableToCode $ArgumentList
                $out += "ArgumentList = $opargs"
                $out += "`n"
            }

            if ($Scriptblock) {
                $out += "Scriptblock = '$scriptblock'"
                $out += "`n"
            }
            else {
                $out += "Filepath = '$Scriptpath'"
                $out += "`n"
            }

            if ($Initialization) {
                $out += "Initialization = '$Initialization'"
                $out += "`n"
            }
            $out += "}"

            $out | Write-Verbose

            #make the filename all lower case
            $fname = "$($Computer.ToUpper())_$(New-Guid).psd1"
            $outFile = Join-Path -Path $Path -ChildPath $fname #.toLower()

            Write-Verbose "Creating datafile $outfile"
            if ($PSCmdlet.ShouldProcess($outFile, "Creating PSRemote Operations File")) {

                if ($PSBoundParameters.ContainsKey("To")) {
                    Write-Verbose "Creating a CMS file"
                    Protect-CmsMessage -To $PSBoundParameters.Item("to") -Content $out -OutFile $outFile
                }
                else {
                    $out | Out-File -FilePath $outFile -Force -Encoding ascii
                }
                if ($Passthru) {
                    Get-Item -Path $outFile
                }
            } #if should process
        } #foreach computer
    } #process
    End {
        Write-Verbose "Ending $($MyInvocation.MyCommand)"
    }
} #close New-PSRemoteOperation

Function Invoke-PSRemoteOperation {
    #You cannot make second hops to other domain machines or systems where you must authenticate
    [cmdletbinding(SupportsShouldProcess)]
    [OutputType("None")]
    [alias('iro')]

    Param(
        [Parameter(
            Position = 0,
            Mandatory,
            HelpMessage = "Enter the path of a remote operation .psd1 file",
            ValueFromPipelineByPropertyName
        )]
        [ValidatePattern("\.psd1$")]
        [ValidateScript( { Test-Path -Path $_ })]
        [Alias("pspath")]
        [string]$Path,

        [Parameter(HelpMessage = "Enter the path for the archived .psd1 file")]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$ArchivePath = $PSRemoteOpArchive
    )

    Begin {
        Write-Verbose "Starting $($MyInvocation.MyCommand)"
        Write-Verbose "Using archive path $ArchivePath"
    } #begin

    Process {
        #this code was updated 13 Nov 2020 to allow running different PowerShell version (Issue #12)

        #convert paths to plain filesystem paths
        $cPath = Convert-Path $path
        Write-Verbose "Processing $cPath"

        $parent = Split-Path -Path $cPath -Parent
        Write-Verbose "Comparing $parent to $ArchivePath"
        #The archive path and path for data file must be different
        if ($parent -eq $ArchivePath) {
            Write-Warning "The archive path must be different from the path to the psd1 file."
            #bail out
            Return
        }

        #test if the file contains a CMS message
        Try {
            Write-Verbose "Testing if a CMS Message"
            $cms = Get-CmsMessage -Path $cPath -ErrorAction stop
            $to = $cms.Recipients.issuerName
            Write-Verbose "Detected CMS recipient $to"
            $raw = (Unprotect-CmsMessage -Path $cPath).split("`n")
            $in = $raw | Out-String | Convert-HashtableString
        }
        Catch {
            Write-Verbose "Not a CMS Message or this is a non-Windows platform. $($_.exception.message)"
            Write-Verbose "Import the data file to create a settings hashtable"
            $in = Import-PowerShellDataFile -Path $cPath
            $raw = Get-Content -Path $cpath
            Write-Verbose ($raw | Out-String)
            $To = $False
        }

        Write-Verbose "Using content:"
        $in | Out-String | Write-Verbose

        if ($PSCmdlet.ShouldProcess($cPath)) {

            <#
            This is probably a bit overly complicated, but it works and supports
            running the operation in different PowerShell versions, so I'm
            happy to let it be.
            #>

            $sb = {
                param($in, $cpath, $ArchivePath, $raw, $to)

                #uncomment for troubleshooting and development
                # $VerbosePreference = "Continue"
                # Start-Transcript c:\work\sb.log
                #set hashtable values to correct type
                if ($in.Scriptblock) {
                    Write-Verbose "Creating scriptblock"
                    $action = [scriptblock]::Create($in.Scriptblock)
                }
                else {
                    Write-Verbose "Getting script contents from $($in.FilePath)"
                    $action = Get-Content -Path $in.FilePath | Out-String
                }

                if ($in.ArgumentList) {
                    #arguments must be entered as a hashtable
                    Write-Verbose "Adding Parameters"
                    $actionParams = $in.ArgumentList

                    #convert True/False to switches.
                    $bool2switch = $actionparams.keys | Where-Object { $actionparams[$_] -match 'true|false' }
                    if ($bool2switch) {
                        foreach ($item in $bool2Switch) {
                            Write-Host "Treating $item as a switch"
                            $txt = $actionParams[$item]
                            Write-Host "converting $txt"
                            if ($txt -eq 'true') {
                                $asSwitch = $True -as [switch]
                            }
                            else {
                                $asSwitch = $False -as [switch]
                            }
                            $actionParams[$item] = $asSwitch
                        }
                    }
                    Write-Verbose ($actionParams | Out-String)
                }

                $psrunspace = [powershell]::Create()

                if ($in.Initialization) {
                    Write-Verbose "Adding initialization"
                    $init = [scriptblock]::Create($in.Initialization)
                    [void]$psrunspace.AddScript($init)
                }

                Write-Verbose "Adding action"
                [void]$psrunspace.Addscript($action)

                if ($actionParams) {
                    Write-Verbose "Adding parameters"
                    [void]$psrunspace.AddParameters($actionParams)
                }

                Write-Verbose ($psrunspace.Commands.commands | Out-String)
                $psrunspace.invoke()
                if ($psrunspace.HadErrors) {
                    $completed = $False
                }
                else {
                    $completed = $True
                }

                $errormsg = """$($psrunspace.Streams.Error.exception.Message)"""
                $psrunspace.dispose()

                #create a results file
                Write-Verbose "Result Data"
                $resultdata = @"
@{
 
"@

                #append the result data to the data file.
                ($Raw | Select-Object -Skip 1 | Select-Object -SkipLast 1 ).Foreach( { $resultData += "$_`n" })

                $resultData += "Completed = '$completed'`n"
                #replace any variables in the errormessage with escaped literals
                $errormsg = $errormsg.Replace('$', '`$')
                $resultData += "Error = $errormsg`n"
                $resultdata += "Date = '$((Get-Date).toUniversalTime()) UTC'`n"

                $resultData += "}"

                $resultdata | Out-String | Write-Verbose

                $filename = Split-Path -Path $cPath -Leaf
                $resultFile = Join-Path -Path $ArchivePath -ChildPath $filename

                Write-Verbose "Creating results file $resultFile"
                if ($to) {
                    Write-Verbose "Create a CMS archive file to $To"
                    Protect-CmsMessage -Content $resultdata -To $to -OutFile $resultFile
                }
                else {
                    $resultdata | Out-File -FilePath $resultFile -Encoding ascii
                }
                #delete
                Write-Verbose "Removing operation file $cPath"
                Remove-Item -Path $cPath -Force
            } #cmd scriptblock

            #invoke a private function to actually run the commands.
            #using a function so it can be mocked for Pester tests
            #powershell.exe -noprofile -command $sb -args $in, $cpath, $ArchivePath, $raw, $to
            _psInvoke -scriptblock $sb -parameters @($in, $cpath, $ArchivePath, $raw, $to)

        } #should process
    }#process

    #old code
    <# Process {
        #convert paths to plain filesystem paths
        $cPath = Convert-Path $path
        Write-Verbose "Processing $cPath"
 
        $parent = Split-Path -Path $cPath -parent
        Write-Verbose "Comparing $parent to $ArchivePath"
        #The archive path and path for data file must be different
        if ($parent -eq $ArchivePath) {
            Write-Warning "The archive path must be different from the path to the psd1 file."
            #bail out
            Return
        }
 
        #test if the file contains a CMS message
        Try {
            Write-Verbose "Testing if a CMS Message"
            $cms = Get-CmsMessage -Path $cPath -ErrorAction stop
            $to = $cms.Recipients.issuerName
            $raw = (Unprotect-CmsMessage -path $cPath).split("`n")
            $in = $raw | Out-String | Convert-HashtableString
        }
        Catch {
            Write-Verbose "Not a CMS Message or this is a non-Windows platform. $($_.exception.message)"
            Write-Verbose "Import the data file to create a settings hashtable"
            $in = Import-PowerShellDataFile -Path $cPath
            $raw = Get-Content -Path $cpath
            Write-Verbose ($raw | Out-String)
            $To = $False
        }
 
        #set hashtable values to correct type
        if ($in.Scriptblock) {
            #$in.Scriptblock = [scriptblock]::Create($in.Scriptblock)
            Write-Verbose "Creating scriptblock"
            $action = [scriptblock]::Create($in.Scriptblock)
        }
        else {
            Write-Verbose "Getting script contents from $($in.FilePath)"
            $action = Get-Content -Path $in.FilePath | Out-String
        }
 
        if ($in.ArgumentList) {
            # $in.ArgumentList = $in.ArgumentList # -split ","
            #arguments must be entered as a hashtable
            Write-Verbose "Adding Parameters"
            $actionParams = $in.ArgumentList
            Write-Verbose ($actionParams | Out-String)
        }
 
        if ($PSCmdlet.ShouldProcess($cPath)) {
 
 
            $psrunspace = [powershell]::Create()
            if ($in.Initialization) {
                Write-Verbose "Adding initialization"
                $init = [scriptblock]::Create($in.Initialization)
                [void]$psrunspace.AddScript($init)
            }
 
            Write-Verbose "Adding action"
            [void]$psrunspace.Addscript($action)
            if ($actionParams) {
                [void]$psrunspace.AddParameters($actionParams)
            }
 
            Write-Verbose ($psrunspace.Commands.commands | Out-String)
            $psrunspace.invoke()
            if ($psrunspace.HadErrors) {
                $completed = $False
            }
            else {
                $completed = $True
            }
 
            $errormsg = """$($psrunspace.Streams.Error.exception.Message)"""
            $psrunspace.dispose()
 
            #create a results file
            Write-Verbose "Result Data"
            $resultdata = @"
@{
 
"@
            #append the result data to the data file.
            ($Raw | Select-Object -skip 1 | Select-Object -SkipLast 1 ).Foreach({ $resultData += "$_`n" })
 
            $resultData += "Completed = '$completed'`n"
            #replace any variables in the errormessage with escaped literals
            $errormsg = $errormsg.Replace('$', '`$')
            $resultData += "Error = $errormsg`n"
            $resultdata += "Date = '$((Get-Date).toUniversalTime()) UTC'`n"
 
            $resultData += "}"
 
            $resultdata | Out-String | Write-Verbose
 
            $filename = Split-Path -Path $cPath -Leaf
            $resultFile = Join-Path -Path $ArchivePath -ChildPath $filename
 
            Write-Verbose "Creating results file $resultFile"
            if ($to) {
                Write-Verbose "Create a CMS archive file to $To"
                Protect-CmsMessage -Content $resultdata -To $to -OutFile $resultFile
            }
            else {
                $resultdata | Out-File -FilePath $resultFile -Encoding ascii
            }
            #delete
            Write-Verbose "Removing operation file $cPath"
            Remove-Item -Path $cPath -Force
        } #should process
    }#process #>


    End {
        Write-Verbose "Ending $($MyInvocation.MyCommand)"
    } #end
} #close Invoke-PSRemoteOperation

Function Get-PSRemoteOperationResult {
    [cmdletbinding(DefaultParameterSetName = "result")]
    [OutputType("RemoteOpResult", ParameterSetName = "result")]
    [OutputType([String[]], ParameterSetName = "raw")]
    [alias('gro')]

    Param(
        [Parameter(
            Position = 0,
            HelpMessage = "Enter a computername to filter on.",
            ParameterSetName = "result"
        )]
        [Parameter(ParameterSetName = "raw")]
        [Alias("cn")]
        [string]$Computername,

        [Parameter(
            Position = 1,
            HelpMessage = "Enter the path to the archive folder.",
            ParameterSetName = "result"
        )]
        [Parameter(ParameterSetName = "raw")]
        [ValidateScript( { Test-Path -Path $_ })]
        [Alias("path")]
        [string]$ArchivePath = $PSRemoteOpArchive,

        [Parameter(ParameterSetName = "result")]
        [Parameter(ParameterSetName = "raw")]
        [Alias("Last")]
        [int]$Newest,

        [Parameter(
            ParameterSetName = "raw",
            HelpMessage = "Display the raw contents of the result file. This can be useful when you get an error parsing the data file."
        )]
        [switch]$Raw
    )

    Write-Verbose "Starting $($myinvocation.MyCommand)"
    Write-Verbose "Getting remote operation results from $ArchivePath"
    if ($computername) {
        Write-Verbose "Using computername $Computername"
        $filter = "$($computername)_*.psd1"
    }
    else {
        $filter = "*.psd1"
    }

    Write-Verbose "Filtering for $filter"
    $data = Get-ChildItem -Path $ArchivePath -Filter $filter | Sort-Object -Property LastWriteTime -Descending

    if ($Newest -gt 0) {
        Write-Verbose "Getting newest $newest results"
        $data = $data | Select-Object -First $Newest
    }
    foreach ($file in $data) {
        Write-Verbose "Processing $($file.fullname)"
        #Test if file is CMS protected
        Try {
            Write-Verbose "Testing for CMS Message"
            $null = Get-CmsMessage -Path $file.Fullname -ErrorAction Stop
            $chash = Unprotect-CmsMessage -Path $file.fullname -ErrorAction Stop | Out-String | Convert-HashtableString
        }
        Catch [System.Security.Cryptography.CryptographicException] {
            Write-Warning "Failed to unprotect the CMSMessage in $($file.fullname). Verify you have the proper DocumentEncryptionCertificate installed."
            Remove-Variable chash -ErrorAction SilentlyContinue
        }
        Catch {
            Write-Verbose "Not a CMS Message or this is a non-Windows platform. $($_.exception.message)"
            $chash = Import-PowerShellDataFile -Path $file.fullname
        }
        if ($Raw) {
            Get-Content -Path $file.Fullname
        }
        else {
            $chash.Add("Path", $file.fullname)
            $obj = New-Object -TypeName psobject -Property $chash
            $obj.psobject.typenames.insert(0, "RemoteOpResult")
            $obj
        }

        #clear the variable so it doesn't accidently get re-used
        Remove-Variable chash -ErrorAction SilentlyContinue

    } #foreach file

    Write-Verbose "Ending $($myinvocation.MyCommand)"
} #end PSGet-RemoteOperationResult

Function Wait-PSRemoteOperation {
    [cmdletbinding(DefaultParameterSetName = "folder")]
    [Outputtype("None")]
    [Alias("wro")]
    Param(
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName,
            HelpMessage = "Specify the path to a PSRemoteOperation file.",
            ParameterSetName = "file"
        )]
        [alias("fullname")]
        [ValidateNotNullOrEmpty()]
        [string]$FilePath,

        [Parameter(ParameterSetName = "folder")]
        [ValidateNotNullOrEmpty()]
        [string]$Path = $PSRemoteOpPath,

        [Parameter(
            ParameterSetName = "folder",
            HelpMessage = "Wait for results from a specific computer"
        )]
        [ValidateNotNullOrEmpty()]
        [alias("cn")]
        [string]$Computername,

        [Parameter(HelpMessage = "Specify a timeout value in seconds between 5 and 300.")]
        [ValidateRange(5, 300)]
        [int32]$Timeout
    )
    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN ] Starting $($myinvocation.mycommand)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using these PSBoundparameters"
        $PSBoundParameters | Out-String | Write-Verbose

        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Using parameter set $($pscmdlet.ParameterSetName)"

        if ($pscmdlet.ParameterSetName -eq 'file') {
            $target = $FilePath
        }
        else {
            if ($Computername) {
                $target = "$PSRemoteOpPath\$($computername)_*.psd1"
            }
            else {
                $target = "$PSremoteOpPath\*.psd1"
            }
        }
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Watching $target"
        if ($Timeout) {
            Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Waiting $Timeout seconds"
        }
        $timer = 0
        do {
            Start-Sleep -Seconds 1
            $timer++
            if ($timeout -AND ($timer -gt $Timeout)) {
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Timeout exceeded"
                Break
            }
        } while (Test-Path -Path $Target )
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Total waiting time $timer seconds."
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END ] Ending $($myinvocation.mycommand)"

    } #end

} #close Wait-PSRemoteOperation

Function Get-PSRemoteOperation {
    [cmdletbinding()]
    [OutputType("RemoteOp")]
    [alias('grop')]

    Param(
        [Parameter(
            Position = 0,
            HelpMessage = "Enter a computername to filter on."
        )]
        [Alias("cn")]
        [string]$Computername,

        [Parameter(
            Position = 1,
            HelpMessage = "Enter the path to the operations folder."
        )]
        [ValidateScript( { Test-Path -Path $_ })]
        [string]$Path = $PSRemoteOpPath
    )

    Write-Verbose "Starting $($myinvocation.MyCommand)"
    Write-Verbose "Getting pending remote operations from $Path"
    if ($computername) {
        Write-Verbose "Using computername $Computername"
        $filter = "$($computername)_*.psd1"
    }
    else {
        $filter = "*.psd1"
    }

    Write-Verbose "Filtering for $filter"
    $data = Get-ChildItem -Path $Path -Filter $filter | Sort-Object -Property LastWriteTime -Descending

    foreach ($file in $data) {
        Write-Verbose "Processing $($file.fullname)"
        #Test if file is CMS protected
        Try {
            Write-Verbose "Testing for CMS Message"
            $null = Get-CmsMessage -Path $file.Fullname -ErrorAction Stop
            $chash = Unprotect-CmsMessage -Path $file.fullname -ErrorAction Stop | Out-String | Convert-HashtableString
        }
        Catch [System.Security.Cryptography.CryptographicException] {
            Write-Warning "Failed to unprotect the CMSMessage in $($file.fullname). Verify you have the proper DocumentEncryptionCertificate installed."
            Remove-Variable chash -ErrorAction SilentlyContinue
        }
        Catch {
            Write-Verbose "Not a CMS Message or this is a non-Windows platform. $($_.exception.message)"
            $chash = Import-PowerShellDataFile -Path $file.fullname
        }

        if ($chash) {
            #convert hashtable into a custom object
            [pscustomobject]@{
                PSTypename   = "PSRemoteOp"
                PSVersion    = $chash.PSVersion
                CreatedOn    = $chash.CreatedOn
                StartTime    = $null
                Scriptblock  = $chash.Scriptblock
                Runtime      = $null
                EndTime      = $null
                Computername = $chash.computername
                CreatedBy    = $chash.CreatedBy
                CreatedAt    = $chash.CreatedAt
                Status       = $chash.status

            }
            <# $chash.Add("Path", $file.fullname)
            $obj = New-Object -TypeName psobject -Property $chash
            $obj.psobject.typenames.insert(0, "RemoteOp")
            $obj #>

        }
        #clear the variable so it doesn't accidently get re-used
        Remove-Variable chash -ErrorAction SilentlyContinue
    } #foreach file

    Write-Verbose "Ending $($myinvocation.MyCommand)"
} #end PSGet-RemoteOperation

Function Register-PSRemoteOpPath {
    [cmdletbinding(SupportsShouldProcess)]
    Param(
        [Parameter(Mandatory, HelpMessage = "Enter a filesystem path for the Remote Operations path")]
        [ValidateScript( { (Test-Path $_ ) -AND ((Get-Item $_).psprovider.name -eq "filesystem") })]
        [string]$PSRemoteOpPath,

        [Parameter(Mandatory, HelpMessage = "Enter a filesystem path for the Remote Operations archive path")]
        [ValidateScript( { (Test-Path $_) -AND ((Get-Item $_).psprovider.name -eq "filesystem") })]
        [string]$PSRemoteOpArchive
    )

    Write-Verbose "Starting $($MyInvocation.MyCommand)"

    $json = Join-Path -Path $PSScriptRoot -ChildPath psremoteoppath.json

    $data = [pscustomobject]@{
        PSRemoteOpPath    = $PSRemoteOpPath
        PSRemoteOpArchive = $PSRemoteOpArchive
        Updated           = (Get-Date -Format f)
    }
    Write-Verbose "Registering this data"
    $data | Out-String | Write-Verbose
    Write-Verbose "to $json"

    if ($PSCmdlet.ShouldProcess($json)) {
        $data | ConvertTo-Json | Out-File -FilePath $json
        #import the data
        Import-PSRemoteOpPath
    } #end whatif

    Write-Verbose "Ending $($MyInvocation.MyCommand)"
} #end Register-PSRemoteOpPath

Function Import-PSRemoteOpPath {
    [cmdletbinding(SupportsShouldProcess)]

    Param(
        [Parameter(HelpMessage = "Enter the path to the remote op path json file.")]
        [ValidateScript( { Test-Path $_ })]
        [string]$Path = (Join-Path -Path $PSScriptRoot -ChildPath psremoteoppath.json)
    )

    Write-Verbose "Ending $($MyInvocation.MyCommand)"
    Write-Verbose "Importing settings from $path"

    $in = Get-Content -Path $json | ConvertFrom-Json

    $in | Out-String | Write-Verbose

    if ($pscmdlet.shouldProcess($in.PSRemoteOpPath, "Set PSRemoteOpPath")) {
        $global:PSRemoteOpPath = $in.PSRemoteOPPath
    }
    if ($pscmdlet.shouldProcess($in.PSRemoteOpArchive, "Set PSRemoteOpArchive")) {
        $global:PSRemoteOpArchive = $in.PSRemoteOpArchive
    }

    Write-Verbose "Ending $($MyInvocation.MyCommand)"

} #end Import-PSRemoteOpPath