Powershell, pas function as expression to remote computer - powershell

I have common helper etc methods on script library, i'd like to use them directly over powershell remoting, how ever i don't know how to pass them to scripts targeted to other machines.
I try pass function as scriptblock and execute it at remote server, on local computer everything works fine:
function test { write-host "abc" }
Invoke-Command -ScriptBlock {Param($1) & $1} -ArgumentList ${Function:Test}
Prints "abc"
However following does not work:
function test { write-host "abc" }
Invoke-Command -ScriptBlock {Param($1) & $1} -ArgumentList ${Function:Test} -ComputerName "OTHERCOMPUTER" -Credential $cred
The term ' write-host "abc" ' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: ( write-host "abc" :String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Any idea what is wrong?
Edit:
For clarification following answer where function itself is defined in scriptblock does not apply because function is taken from common script file located only on computer where remote is used. They are like "GetConfigurationFromAppConfig","SelectFromDatabase" and so on.
If i write function to every script block, then i have to copypaste same code over and over again everywhere. I don't want to use "common" script location where every computer can load scripts inside block either (for example . "\SharedLocation\") because it is fragile (lots of depencies) and it must be made for every server targeted for remoting (lots of upkeep).

Invoke-Command -ScriptBlock {function test { write-host "abc" }; test} -ComputerName "OTHERCOMPUTER" -Credential $cred

In the instances where I need to leverage a library of custom functions/resources, I just use Import-Module within my scriptblock. This seems like the cleanest implementation to me....and I have access to hundreds of custom functions here at work.

Another option is to use [ScrtiptBlock]::Create() to build your script using the function definitions that are already declared in your local session. This saves you having to manually re-create all the code in the functions inside the remote script invocation:
function test { write-host "abc" }
$SB = [ScriptBlock]::Create(#"
function test {$Function:test}
test
"#)
$SB
function test { write-host "abc" }
test
Then just invoke $SB on the remote computer.
Edit:
Per the additional comments, if you have a library of script files on the remote computer where the functions are declared, you can just dot-source them in your script block:
$SB = {
. 'c:\scripts\myfunctions.ps1'
test
}
Invoke-Command -ScriptBlock $SB -ComputerName 'OTHERCOMPUTER'

Related

$Using:var in Start-Job within Invoke-Command

I am using Invoke-Command, and within the -ScriptBlock I am using Start-Job. I have to use $Using:var within Start-Job but the session is looking for the declared variables in the local session (declared before Invoke-Command). Here's a very brief example of what I'm doing:
Invoke-Command -ComputerName $computer -ScriptBlock {
$sourcePath = 'C:\Source'
$destPath = 'C:\dest.zip'
$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
$includeBaseDirectory = $false
Start-Job -Name "compress_archive" -ScriptBlock {
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory("$using:sourcePath","$using:destPathTemp",$using:compressionLevel,$using:includeBaseDirectory)
}
}
Invoke-Command : The value of the using variable '$using:sourcePath' cannot be retrieved because it has not been set in the local session.
At line:1 char:1
+ Invoke-Command -ComputerName vode-fbtest -ScriptBlock {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [Invoke-Command], RuntimeException
+ FullyQualifiedErrorId : UsingVariableIsUndefined,Microsoft.PowerShell.Commands.InvokeCommandCommand
If I omit $using when calling variables in the Start-Job -ScriptBlock {} then I get a Cannot find an overload for "CreateFromDirectory" and the argument count: "4". error because the variables are not defined in that scope.
Is there a way to use $using for variables within the remote session rather than the local one, or possibly another scope I can specify that would source variables from the remote session? I could declare these variables locally before the Invoke-Command to fix this but that would require a significant bit of work due to the variables containing dynamic values (all of this is in a foreach ($obj in $objects), the data for which is retrieved on the remote computer so I would need to restructure the whole script if I can't make this work).
I'm using PS v5.1 on Windows Server 2012 R2 (both source host and -ComputerName host on which the command is invoked) if that makes any difference.
Looking at this answer I see that you can expose variables to lower level script blocks but I need to actually declare the variable from within the remote session. The value needs to come from the computer on which the remote session is running. Can you declare the variable from within the remote session in a fashion that makes it available to script blocks within the top-level script block?
PetSerAl, as countless times before, has provided the crucial pointer in a terse comment on the question:
You need to:
use [scriptblock]::Create() to create the script block to pass to Start-Job dynamically, from a string
make the [scriptblock]::Create() call inside the Invoke-Command script block, because only that ensures that the variables declared in there are the ones referenced in the [scriptblock]::Create()-created script block via the $using: scope specifier.
By contrast, if you use a script-block literal, { ... } with Start-Job, as in your attempt, the $using: references do not refer to the Invoke-Command script block's scope, but to the scope of the caller of Invoke-Command, i.e. to the variables visible to the code that makes the overall Invoke-Command call.
Ideally, the expansion of $using:... references would be smart enough to handle nested scopes, as in this case, but that is not the case as of PowerShell Core 7.0.0-preview.3.
Caveat: As PetSerAl points out, if you use Invoke-Command with a command-scoped ad-hoc session (implied by using -ComputerName) - rather than a longer-lived session created prior with New-PSSession and passed to Invoke-Command with -Session - the background job gets terminated when the Invoke-Command call returns, before it (likely) has a chance to finish. While you could pipe the Start-Job call to ... | Receive-Job -Wait -AutoRemove, that would only be worth it if you started multiple jobs.
Therefore:
Invoke-Command -ComputerName $computer -ScriptBlock {
# Inside this remotely executing script block, define the variables
# that the script block passed to Start-Job below will reference:
$sourcePath = 'C:\Source'
$destPath = 'C:\dest.zip'
$compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
$includeBaseDirectory = $false
# Define the Start-Job script block as *literal* (here-)*string*, so as
# to defer interpretation of the $using: references, and then
# construct a script block from it using [scriptblock]::Create(), which
# ties the $using: references to *this* scope.
$jobSb = [scriptblock]::Create(
#'
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::CreateFromDirectory("$using:sourcePath","$using:destPathTemp",$using:compressionLevel,$using:includeBaseDirectory)
'#
)
Start-Job -Name "compress_archive" -ScriptBlock $jobSb
}

Not able to access a commandlet in the script block of start-job

We are trying to write a background job in PowerShell using Start-Job. This job consumes a cmdlet. For some reasons, the cmdlet is not recognized. However the same cmdlet works when accessed directly.
Code Snippet : Start-Job -ScriptBlock { }
Error :
The term 'commandletName' is not recognized as the name of a cmdlet,
function, script file, or operable program. Check the spelling of the
name, or if a path was included, verify that the path is correct and
try again. + CategoryInfo : ObjectNotFound: (commandletName:String)
[], CommandNotFoundException + FullyQualifiedErrorId :
CommandNotFoundException + PSComputerName : localhost
Do i need to import something separately specific for background jobs?
Start-Job starts a whole new PowerShell session, so any cmdlets that have been imported/created have to be re-imported in the job itself.
You could pull this in using the $using scope.
function Write-HelloWorld {
"Hello World"
}
Start-Job -ScriptBlock {
${function:Write-HelloWorld} = ${using:function:Write-HelloWorld}
Write-HelloWorld
}

Powershell - usmt loadstate.exe - CommandNotFoundException

Im stuck on USMT migration. Here is my code:
$scriptA = "\\PL-HVEZDAP\backup\USMTBin\scanstate.exe \\PL-HVEZDAP\backup /ue:* /ui:$name /o /i:'\\PL-HVEZDAP\backup\USMTBin\miguser.xml' /i:'\\PL-HVEZDAP\backup\USMTBin\migapp.xml' /c"
$scriptB = "\\PL-HVEZDAP\backup\USMTBin\loadstate.exe \\PL-HVEZDAP\backup /ue:* /ui:$name /i:'\\PL-HVEZDAP\backup\USMTBin\miguser.xml' /i:'\\PL-HVEZDAP\backup\USMTBin\migapp.xml' /c"
$scriptA = [scriptblock]::Create($scriptA)
$scriptB = [scriptblock]::Create($scriptB)
Invoke-Command -ComputerName $computer -scriptblock $scriptA
Invoke-Command -ComputerName $remcomputer -scriptblock $scriptB
There is a problem on last row - I get error:
The term '\\PL-HVEZDAP\backup\USMTBin\loadstate.exe' is not recognized as the name of a cmdlet, function, script file,
or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try
again.
+ CategoryInfo : ObjectNotFound: (\\PL-HVEZDAP\ba...n\loadstate.exe:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
but if I run $scriptb = get-process, everything works like charm...
Can somebody help me with this? Thanks a lot.
Looks like a kerberos double-hop problem. Once you connect to a remote computer with Invoke-Command, you can't use any commands that implicitly use your authentication again (this includes cmdlets, executables, and connecting to file shares).
This is a function of how Kerberos is designed. You can use a different type of authentication called CredSSP with Invoke-Command to delegate credentials but this requires some set up on both ends.
Often it's easier to re-think where the pieces are stored and/or executed.
Since you make extensive use of a file share, you might consider not using Invoke-Command to a remote machine and instead executing the backup script locally on the target machine.
One way to do this, while still being able to store the script centrally is to make a scheduled task on the target machine that runs this script. It can even be pushed out via GPO if you're in a domain.
Then you can start the task when you want that code to run.
You can even use Invoke-Command -ComputerName $remcomputer -ScriptBlock { schtasks.exe /Run /TN "My Task" } and that will work.

Powershell Invoke-Command with Variable for Script Name

I need to execute a Powershell script on a remote machine from a local script. Problem is, I don't know the path or filename of the remote script until runitime.
I've tried the following line in my local script:
Invoke-Command -ComputerName $TargetServer -ScriptBlock { & ($TargetMSI) '$MSI' 'C:\Program Files (x86)\Vasanta.Int.MIS' 'Dev' }
Problem is this returns the error: The expression after '&' in a pipeline element produced an invalid object.
If replace the $TargetMSI with a hard-coded string literal then it works fine.
Can anyone please tell me what I need to change?
When you Invoke-Command in v2 there is no direct way to pass variables to scriptblock. You need to use -ArgumentList + param () in scriptblock combo:
Invoke-Command -ScriptBlock { param ($TargetMSI, $MSI) & $TargetMSI '$MSI' } -ArgumentList $TargetMSI, $MSI
this is fixed/ improved in v3 with $using:localvariable syntax.

Trouble Executing a .cmd file during a PS-Session

Im trying to execute a .bat file remotely in a powershell script. The code in my script file looks something like this:
$server
$cred
# Both of these methods are strings pointing to the location of the scripts on the
# remote server ie. C:\Scripts\remoteMethod.cmd
$method1
$method2
$scriptArg
Enter-PSSession $server -Credential $cred
Invoke-Command {&$method1}
if($?)
{
Invoke-Command {&$method2 $args} -ArgumentList $scriptArg
}
Exit-PSSession
But whenever I run it I get
The term 'C:\Scripts\remoteMethod.cmd' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
This happens even if i switch around the order the methods are called in.
I found this question Using Powershell's Invoke-Command to call a batch file with arguments and have tried the accepted answers to no avail.
I wouldn't have expected the $method1 and $method2 variables to be available inside the scriptblock for Invoke-Command. Usually you would have to pass those in via the -ArgumentList parameter e.g.:
Invoke-Command {param($method, $arg) &$method $arg} -Arg $method2 $scriptArg
But it seems in your case the path to the CMD file is making it across the remote connection. Try this to see if the problem is related to a modified ComSpec environment variable on the remote machine:
Invoke-Command {param($method, $arg) cmd.exe /c $method $arg} -Arg $method2, $scriptArg