Powershell Passing Function to remote command - powershell

I am trying to pass local function to remote code but not getting any success. Below is my code.
#variables defined over here
...
function comparehash ($source, $destination)
{
$sourcefiles = #{}
...
}
$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
$session = New-PSSession -ComputerName $srv -port 22 -Credential $cred –Authentication CredSSP
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application)
#Write-Host "This is" $source
# Take backup of the site first
Copy-Item $source\$application $destination -Recurse -force
${function:comparehash}
} -ArgumentList $site_path_local, $backup_path, $app
Remove-PSSession -Session $session
This code is copying source files to destination. Function has been created to validate md5 sum of the copied files. When I run the script script runs fine but it doesn't call the function code. Is there anything additional need to be done to call function?
Update
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application, $fundef, $comparefunc )
Write-Host "This is" $source, $destination
# Take backup of the site first
#Copy-Item $source\$application $destination -Recurse -force
[ScriptBlock]::($comparefunc).Invoke($source,$destination)
#comparehash $site_path_local $backup_path
} -ArgumentList $site_path_local, $backup_path, $app, ${function:comparehash}
Remove-PSSession -Session $session
Above code is throwing following error:
PS C:\Windows\system32> C:\Users\vijay.patel\Documents\myscripts\Env_Refresh.ps1
This is F:\inetpub F:\Env_Backup\Refresh_Backup\
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (Invoke:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : 172.16.82.124

Since the function is defined in your own local scope, you'll have to pass it along to the remote session as well. Through testing, it seems that it gets passed as a string, but you can use [scriptblock]::Create() to "recreate" it in the remote session:
Invoke-Command -Session $session -ScriptBlock {
param( $source, $destination, $application, $comparefunc)
# do stuff
# Now invoke the function that was provided
[ScriptBlock]::Create($comparefunc).Invoke($source,$destination)
} -ArgumentList $site_path_local, $backup_path, $app, ${function:comparehash}

I found the $Using command can be your friend here. If you keep the remote PSSession open, you can transfer all the functions you will need one at a time, then invoke the final scriptblock that calls/uses those functions.
function comparehash($source, $destination)
{
Write-Output "I like hash";
if( $source -eq $destination) { PackThePipe $source; }
}
function PackThePipe($hash)
{
Write-Output "Pushing $hash through the pipe";
}
$session = New-PSSession -ComputerName $srv -port 22 -Credential $cred –Authentication CredSSP
#create the functions we need remotely
Invoke-Command $session { Invoke-Expression $Using:function:comparehash.Ast.Extent.Text; }
Invoke-Command $session { Invoke-Expression $Using:function:PackThePipe.Ast.Extent.Text; }
#call the scriptblock that utilizes those functions
Invoke-Command $session { $s = "12345"; $d="12345"; comparehash $s $d; }
Remove-PSSession $session;

Related

PowerShell cannot use New-PSSessions right after Invoke-CimMethod (The runspace state is not valid for this operation)

The two code below works independently, however, they cannot work in the same script. I really need help, there's got to be something incompatible.
The first part of my script uses Invoke-CimMethod to Enable-PSRemoting, and it works.
Variables
$hostname = 'PC1'
$Session = New-PSSession $hostname
$DestinationPath = "C:\windows\temp"
Part 1
$SessionArgs = #{
ComputerName = $hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
The second part of my code works if the first part above is not present. It is to create a TEMP folder, and then copy an entire folder into TEMP.
Part 2
Invoke-Command -Session $Session -ScriptBlock { Param($Destination) New-Item -Path $Destination -ItemType Directory -ErrorAction SilentlyContinue} -ArgumentList $DestinationPath
Copy-Item -Path "\\shared\folder\foo\bar" -ToSession $Session -Destination "C:\windows\temp\" -recurse -force
Error
Copy-Item : The runspace state is not valid for this operation.
What's weird is I've inserted the Invoke-CimMethod to many other scripts that does similar things and it works fine, like for example
Example of it working
$env:hostname
$env:process
$SessionArgs = #{
ComputerName = $env:hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
$session = New-PSSession $env:hostname
ipconfig
Invoke-Command -Session $session -ScriptBlock {param($process) Stop-Process -ProcessName $process -Force} -ArgumentList $env:process
$Session | Remove-PSSession
Please help! I've tried everything, I even tried Get-CimSession | Remove-CimSession but that didn't work. Why is it incompatible?
I was able to fix this issue by putting the variable
$Session = New-PSSession $hostname
Right before Invoke-Command because I think when I sent enable-pssession it resets the connection.

PowerShell - Why do I have to state the New-PSSession Variable again?

So I am running an Invoke-CimMethod to push an Enable-PSRemoting command to target computer. Then run an Invoke-Command using PS-Session as a parameter. The two scripts work separately, but if I run them together I keep getting this error:
Copy-Item : The runspace state is not valid for this operation.
I had to restate the $session variable like so in order for it to run. I have bolded and highlighted the line below. My question is why?
$env:hostname = 'PC1'
$Session = New-PSSession $env:hostname
$DestinationPath = "C:\windows\temp"
$SessionArgs = #{
ComputerName = $env:hostname
Credential = $credential
SessionOption = New-CimSessionOption -Protocol Dcom
}
$MethodArgs = #{
ClassName = 'Win32_Process'
MethodName = 'Create'
CimSession = New-CimSession #SessionArgs
Arguments = #{
CommandLine = "powershell Start-Process powershell -ArgumentList 'Enable-PSRemoting -Force'"
}
}
Invoke-CimMethod #MethodArgs
Invoke-Command -Session $Session -ScriptBlock { Param($Destination) New-Item -Path $Destination -ItemType Directory -ErrorAction SilentlyContinue} -ArgumentList $DestinationPath
Copy-Item -Path "\\shared\drive\foo\bar\" -Destination "C:\windows\temp\ZScaler" -Recurse -force
############Restated here#############
$Session = New-PSSession $env:hostname
############Restated here#############
Invoke-Command -Session $session -ScriptBlock {
$msbuild = "C:\Windows\Temp\Installer\Installer.msi"
$arguments = "/quiet"
Start-Process -FilePath $msbuild -ArgumentList $arguments -Wait -Verbose
}
$Session | Remove-PSSession

how to pass command line parameters to invoked session in powershell 2.0

How can i pass command line parameters to a session which is invoked using 'invoke-command' in powershell 2.0
my script:
param(
[string]$hostname = 'my_server_name'
)
function createSession($hostname){
return New-PSSession -ComputerName $hostname -Credential $env:UserDomain\$env:UserName
}
$session = createSession $hostname
invoke-command -Session $session -ScriptBlock {
write-host $hostname
write-host $using:hostname
write-host $script:hostname
write-host '**test text**'
}
Exit-PSSession
Output: (I'm getting empty string if i print the parameter value directly.)
**test text**
use param block
$hostname = $env:computername
Invoke-Command -ScriptBlock { param($hostname)
Write-OutPut $hostname
} -ArgumentList $hostname
param(
[string]$hostname = 'my_server_name'
)
function createSession($hostname){
return New-PSSession -ComputerName $hostname -Credential $env:UserDomain\$env:UserName
}
$session = createSession $hostname
invoke-command -Session $session -ScriptBlock {
$hostname=$using:hostname
or
$hostname=$script:hostname
write-host $hostname
write-host '**test text**'
}
Exit-PSSession
Hope any one of the above helps,If Not Please look at the concept of scoping variables in powershell,May be they will help
Powershell variable scoping

Remote Registry using Enter-PSSession

I am trying to read strings in a remote registry. When I run the script I am working on, it connects to the workstation in the list, but it only reads the local computer when running, not the remote. any Ideas?
#create open dialog box
Function Get-FileName($initialDirectory)
{
[void] [Reflection.Assembly]::LoadWithPartialName( 'System.Windows.Forms' );
$d = New-Object Windows.Forms.OpenFileDialog;
$d.ShowHelp = $True;
$d.filter = "Comma Separated Value (*.csv)| *.csv";
$d.ShowDialog( ) | Out-Null;
$d.filename;
}
# Set Variables with arguments
$strFile = Get-FileName;
$strComputer = Get-Content $strFile;
$date = Get-Date -Format "MM-dd-yyyy";
$outputFile = "C:\PowerShell\Reports";
$cred = Get-Credential
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
foreach($computer in $strComputer)
{
Enter-PSSession $computer -Credential $cred
..
..
}
The above code won't work. Enter-PSSession is not for using in a script. Anything written after that in a script won't run.
Instead, use Invoke-Command and pass rest of the script block as a parameter value. For example,
foreach ($computer in $strComputer) {
Invoke-Command -ComputerName $computer -Credential $cred -ScriptBlock {
Set-Location HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability
$systemInfo = Get-Item -Name LastComputerName
Write-Host $systemInfo
}
}
As the comments already explained, Enter-PSSession is for interactive use. To read remote registry entries, there are several ways.
Use plain reg.exe, it works well enough. Like so,
foreach($computer in $strComputers) {
reg query \\$computer\hklm\software\Microsoft\Windows\CurrentVersion\Reliability /v LastComputerName
}
Use PSSessions. Create a session and Invoke-Command to read registry. Like so,
function GetRegistryValues {
param($rpath, $ivalue)
Set-Location $rpath
$systemInfo = (Get-ItemProperty .).$ivalue
Write-Host $systemInfo
}
$session = New-PSSession -ComputerName $computer
Invoke-Command -Session $session -Scriptblock ${function:GetRegistryValues} `
-argumentlist "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability",`
"LastComputerName"
Remove-PSSession $session
Use .Net classes, Microsoft.Win32.RegistryKey. Like so,
$sk = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $server)
$k = $sk.opensubkey("SOFTWARE\Microsoft\Windows\CurrentVersion\Reliability", $false)
write-host $k.getvalue("LastComputerName")

Powershell Remoting: using imported module cmdlets in a remote pssession

Is there a way to use modules that were imported in a local session in a remote session? I looked at import-pssession, but I don't know how to get the local session. Here's a sample of what I want to do.
import-module .\MyModule\MyModule.ps1
$session = new-pssession -computerName RemoteComputer
invoke-command -session $session -scriptblock { Use-CmdletFromMyModule }
Also, I do not want to import-module in the remote session, as the ps1 files are not on that server.
I ended up hacking this to work. What I did was create a local session, import modules into that session and used import-pssession to import modules from the created local session into the remote session. This is slow. If anyone has a better way of doing this, or if someone knows how to get an instance of the base session I'd love to hear from you!
Remoting.psm1
function Export-ModuleToSession {
Param(
[ValidateNotNull()]
$session,
[ValidateNotNull()]
$modules
)
$computername = $env:computername
$modulesToImport = get-module -name $modules
invoke-command -session $session -argumentlist #($computername, $modulesToImport) -scriptblock {
Param(
$computername,
$modules
)
write-host ("Creating Temp Session On: " + $computername)
$localSession = New-psSession -computername $computername
$modules | foreach-object {
if($_.ModuleType -ne "Binary") {
$path = $_.path
}
else {
$path = join-path (split-path $_.Path) ("{0}.psd1" -f $_.name)
}
invoke-command -session $localSession -argumentList $path -scriptblock {
Param(
$path
)
$initializeDefaultBTSDrive = $false
set-executionpolicy unrestricted
write-host ("Importing Module To Temp Session: " + $path)
import-module $path
}
}
$initializeDefaultBTSDrive = $false
$modules | foreach-object {
write-host ("Exporting Module: " + $_.name)
import-psSession -session $localSession -Module $_.name | out-null
}
}
}
MyModule.psm1
function MyCmdlet {}
RemotingTest.ps1
import-module .\remoting.psm1
import-module .\MyModule.psm1
try
{
$remoteSession = New-PsSession -computerName "RemoteComputer"
Export-ModuleToSession -session $remoteSession -modules "MyModule"
Invoke-Command -session $remoteSession -scriptblock { MyCmdlet } -verbose -ea Stop
}
finally
{
Remove-PsSession $remoteSession -ea Continue
Remove-Module "Remoting" -ea Continue
Remove-Module "MyModule" -ea Continue
}
As an alternate to what Jonathan mentions, if you have source modules you want to push over the wire, then you can do that without too much trouble. If you have binaries, you might be able to do something similar.but I'd say all bets are off there. Essentially you push the files over as params in a hash, write to temp, then import.
function Export-SourceModulesToSession
{
Param(
[Management.Automation.Runspaces.PSSession]
[ValidateNotNull()]
$Session,
[IO.FileInfo[]]
[ValidateNotNull()]
[ValidateScript(
{
(Test-Path $_) -and (!$_.PSIsContainer) -and ($_.Extension -eq '.psm1')
})]
$ModulePaths
)
$remoteModuleImportScript = {
Param($Modules)
Write-Host "Writing $($Modules.Count) modules to temporary disk location"
$Modules |
% {
$path = ([IO.Path]::GetTempFileName() + '.psm1')
$_.Contents | Out-File -FilePath $path -Force
"Importing module [$($_.Name)] from [$path]"
Import-Module $path
}
}
$modules = $ModulePaths | % { #{Name = $_.Name; Contents = Get-Content $_ } }
$params = #{
Session = $Session;
ScriptBlock = $remoteModuleImportScript;
Argumentlist = #(,$modules);
}
Invoke-Command #params
}
Call like
$session = New-PSSession -ComputerName Foo
Export-SourceModulesToSession $session -ModulePaths '.\module.psm1','.\module2.psm1'
Also theoretically possible, exporting a current localhost session to module and pushing that over the wire -- untested pseudo-code. This might not work...
$localSession = New-PSSession #defaults to localhost
# if you don't have modules automatically loading in the profile, etc, then manually load them
Invoke-Command -Computer $localSession -ScriptBlock { Import-Module 'foo'; Import-Module '.\module.ps1' }
Export-PSSession $localSession -OutputModule TempLocalModule
#now that you have TempLocalModule written out, it's possible you can send that thing across the wire in the same way
in case this helps:
if you can switch to PowerShell 3.0, then
the Get-Module and Import-Module cmdlets support modules on remote computers:
http://technet.microsoft.com/en-us/library/hh857339.aspx#BKMK_REM
It should be possible to run PowerShell on remote computer, that performs Import-Module,
without having any ps1 scripts on the remote computer.
So I was looking for something similar... In my case I just needed to export a single function to a remote session... this is what I came up with. Maybe you could loop over it to try it. It doesn't work with internal commands, but it does work on functions in custom modules (in the testing I've done).
function Export-FunctionToSession
{
[CmdletBinding()]
[Alias()]
[OutputType([int])]
Param
(
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$Session,
[Parameter(Mandatory=$true,
ValueFromPipelineByPropertyName=$true,
Position=0)]
$FunctionName
)
$script = "Function $functionName(){" + (Get-Command $functionName).definition + '}'
$scriptBlock = {Invoke-Expression $using:script}
Invoke-Command -Session $session -ScriptBlock $scriptBlock
}
I don't believe you can. You can go the other way - import commands loaded on the remote machine into your remote session running on the local machine. You can specify a script to invoke-command and it will copy that script over to the remote machine and run it. But if you require snapins or additional modules, you will need to make sure those are installed on each remote machine and then load them into the remote session via commands or your script.
I would suggest something like:
$rs = New-PSSession -ComputerName "RemoteComputer"
Invoke-Command -Session $rs -scriptblock {import-module ActiveDirectory}
Import-PSSession -Session $rs -Module ActiveDirectory
Since then, you can use ActiveDirectory cmdlets in your session.
Hope this helps.
use credssp authentication
invoke-command -computername $localSession -Credential $Credential -Authentication Credssp