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" )] [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()] [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 ) 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 { foreach ($Computer in $Computername) { Write-Verbose "Creating a remote operations file for $($computer.toUpper())" #define a here string for the psd1 content $out = @" @{ CreatedOn = '$(hostname)' CreatedBy = '$(whoami)' CreatedAt = '$((Get-Date).toUniversalTime()) UTC' Computername = '$($Computer.ToUpper())' "@ 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 ($To) { Write-Verbose "Creating a CMS file" Protect-CmsMessage -To $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 { #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)) { #TODO - this should be turned into private functions so it can be tested with Pester $psrunspace = [powershell]::Create() if ($in.Initialization) { Write-Verbose "Adding initialization" $init = [scriptblock]::Create($in.Initialization) $psrunspace.AddScript($init) | Out-Null } Write-Verbose "Adding action" $psrunspace.Addscript($action) | Out-Null if ($actionParams) { $psrunspace.AddParameters($actionParams) | Out-Null } 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 # } #finally } #should process }#process End { Write-Verbose "Ending $($MyInvocation.MyCommand)" } #end } #close Invoke-PSRemoteOperation Function Get-PSRemoteOperationResult { [cmdletbinding()] [OutputType("RemoteOpResult")] [alias('gro')] Param( [Parameter(Position = 0, HelpMessage = "Enter a computername to filter on.")] [string]$Computername, [Parameter(Position = 1, HelpMessage = "Enter the path to the archive folder.")] [ValidateScript( {Test-Path -path $_})] [Alias("path")] [string]$ArchivePath = $PSRemoteOpArchive, [Alias("Last")] [int]$Newest ) 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" Get-CmsMessage -Path $file.Fullname -ErrorAction Stop | Out-Null $hash = Unprotect-CmsMessage -Path $file.fullname | Out-String | Convert-HashtableString } Catch { Write-Verbose "Not a CMS Message or this is a non-Windows platform. $($_.exception.message)" $hash = Import-PowerShellDataFile -Path $file.fullname } $hash.Add("Path", $file.fullname) $obj = New-Object -typename psobject -Property $hash $obj.psobject.typenames.insert(0, "RemoteOpResult") $obj } Write-Verbose "Ending $($myinvocation.MyCommand)" } #end PSGet-RemoteOperationResult |