I want to have in a .ps1 file some code that creates a PSSession that can be used in other .ps1 scripts (in order to avoid code duplication).
At first I thought i need a function that creates a PSSession and returns it but I am confused about how they the function outputs work.
Here is my function:
function newRemoteSession
{
param([string]$ipAddress)
$accountName = 'admin'
$accountPassword = 'admin'
$accountPasswordSecure = ConvertTo-SecureString $accountPassword -AsPlainText -Force
$accountCredential = New-Object System.Management.Automation.PSCredential ($accountName, $accountPasswordSecure)
Try
{
$remoteSession = New-PSSession -ComputerName $ipAddress -UseSSL -Credential $accountCredential -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck) -ErrorAction Stop
}
Catch [System.Management.Automation.RuntimeException] #PSRemotingTransportException
{
Write-Host 'Could not connect with default credentials. Please enter credentials...'
$remoteSession = New-PSSession -ComputerName $ipAddress -UseSSL -Credential (Get-Credential) -SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck) -ErrorAction Stop
Break
}
return $remoteSession
}
However when I call $s = newRemoteSession(192.168.1.10), $s is empty.
When I run a script with
Write-Host '00'
$s = newRemoteSession('192.168.1.10')
$s
Write-Host '02'
function newRemoteSession
{
........
Write-Host '01'
$remoteSession
}
I get only '00' in the console, but I know the function runs because I get the credential prompt.
EDIT:
Ok, now it works:
The Break in the Catch was stopping everything.
The function call must be without parenthesis.
The second code was wrong because the function must be defined before the call.
You've found the problems yourself, but since they're common pitfalls, let me elaborate a little:
Only ever use break / continue inside a loop (for, foreach, while, do) or in the branch handlers of a switch statement.
Otherwise, PowerShell looks up the call stack for an enclosing loop and, in the absence of one, exits the script.
This is what happened with the break in your catch block.
Do not use (...) to enclose the list of function (command) arguments and do not separate arguments with ,:
In PowerShell, functions - just like cmdlets - are called with shell-like syntax, without parentheses and with whitespace-separated arguments (, is only used to construct an array to be passed as a single argument)[1]
.
# WRONG: method-invocation syntax does NOT apply to *functions*
# (It *happens* to work *in this case*, however, because only a
# a *single* argument is passed and the (...) is a no-op in this case,
# but this syntax should never be used.)
newRemoteSession('192.168.1.10')
# OK: Shell-like syntax, which PowerShell calls *argument mode*:
# Note that the argument needn't be quoted, because it
# contains no shell metacharacters.
# If there were additional arguments, you'd separate them with *whitespace*
newRemoteSession 192.168.1.10
# BETTER: You can even use *parameter names* (again, as with cmdlets),
# which helps readability.
newRemoteSession -ipAddress 192.168.1.10
In PowerShell, functions must be defined before you can call them.
You initially didn't notice, because a previous definition of your newRemoteSession function happened to exist.
[1] For more information about PowerShell's two fundamental parsing modes -
argument mode and expression mode - see this answer of mine.
Related
I have a script that I can run remotely via Invoke-Command
Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
-FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1
As long as I use default parameters, it works fine. However, the script has 2 named [switch] parameters (-Debug and -Clear)
How can I pass the switched parameters via the Invoke-Command? I've tried the -ArgumentList but I'm getting errors so I must have the syntax wrong or something. Any help is greatly appreciated.
-ArgumentList is based on use with scriptblock commands, like:
Invoke-Command -Cn (gc Servers.txt) {param($Debug=$False, $Clear=$False) C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 } -ArgumentList $False,$True
When you call it with a -File it still passes the parameters like a dumb splatted array. I've submitted a feature request to have that added to the command (please vote that up).
So, you have two options:
If you have a script that looked like this, in a network location accessible from the remote machine (note that -Debug is implied because when I use the Parameter attribute, the script gets CmdletBinding implicitly, and thus, all of the common parameters):
param(
[Parameter(Position=0)]
$one
,
[Parameter(Position=1)]
$two
,
[Parameter()]
[Switch]$Clear
)
"The test is for '$one' and '$two' ... and we $(if($DebugPreference -ne 'SilentlyContinue'){"will"}else{"won't"}) run in debug mode, and we $(if($Clear){"will"}else{"won't"}) clear the logs after."
Without getting hung up on the meaning of $Clear ... if you wanted to invoke that you could use either of the following Invoke-Command syntaxes:
icm -cn (gc Servers.txt) {
param($one,$two,$Debug=$False,$Clear=$False)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 #PSBoundParameters
} -ArgumentList "uno", "dos", $false, $true
In that one, I'm duplicating ALL the parameters I care about in the scriptblock so I can pass values. If I can hard-code them (which is what I actually did), there's no need to do that and use PSBoundParameters, I can just pass the ones I need to. In the second example below I'm going to pass the $Clear one, just to demonstrate how to pass switch parameters:
icm -cn $Env:ComputerName {
param([bool]$Clear)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $(Test-Path $Profile)
The other option
If the script is on your local machine, and you don't want to change the parameters to be positional, or you want to specify parameters that are common parameters (so you can't control them) you will want to get the content of that script and embed it in your scriptblock:
$script = [scriptblock]::create( #"
param(`$one,`$two,`$Debug=`$False,`$Clear=`$False)
&{ $(Get-Content C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -delimiter ([char]0)) } #PSBoundParameters
"# )
Invoke-Command -Script $script -Args "uno", "dos", $false, $true
PostScript:
If you really need to pass in a variable for the script name, what you'd do will depend on whether the variable is defined locally or remotely. In general, if you have a variable $Script or an environment variable $Env:Script with the name of a script, you can execute it with the call operator (&): &$Script or &$Env:Script
If it's an environment variable that's already defined on the remote computer, that's all there is to it. If it's a local variable, then you'll have to pass it to the remote script block:
Invoke-Command -cn $Env:ComputerName {
param([String]$Script, [bool]$Clear)
& $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)
My solution to this was to write the script block dynamically with [scriptblock]:Create:
# Or build a complex local script with MARKERS here, and do substitutions
# I was sending install scripts to the remote along with MSI packages
# ...for things like Backup and AV protection etc.
$p1 = "good stuff"; $p2 = "better stuff"; $p3 = "best stuff"; $etc = "!"
$script = [scriptblock]::Create("MyScriptOnRemoteServer.ps1 $p1 $p2 $etc")
#strings get interpolated/expanded while a direct scriptblock does not
# the $parms are now expanded in the script block itself
# ...so just call it:
$result = invoke-command $computer -script $script
Passing arguments was very frustrating, trying various methods, e.g.,
-arguments, $using:p1, etc. and this just worked as desired with no problems.
Since I control the contents and variable expansion of the string which creates the [scriptblock] (or script file) this way, there is no real issue with the "invoke-command" incantation.
(It shouldn't be that hard. :) )
I suspect its a new feature since this post was created - pass parameters to the script block using $Using:var. Then its a simple mater to pass parameters provided the script is already on the machine or in a known network location relative to the machine
Taking the main example it would be:
icm -cn $Env:ComputerName {
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}
I needed something to call scripts with named parameters. We have a policy of not using ordinal positioning of parameters and requiring the parameter name.
My approach is similar to the ones above but gets the content of the script file that you want to call and sends a parameter block containing the parameters and values.
One of the advantages of this is that you can optionally choose which parameters to send to the script file allowing for non-mandatory parameters with defaults.
Assuming there is a script called "MyScript.ps1" in the temporary path that has the following parameter block:
[CmdletBinding(PositionalBinding = $False)]
param
(
[Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
[Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
[Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)
This is how I would call this script from another script:
$params = #{
MyNamedParameter1 = $SomeValue
MyNamedParameter2 = $SomeOtherValue
}
If ($SomeCondition)
{
$params['MyNamedParameter3'] = $YetAnotherValue
}
$pathToScript = Join-Path -Path $env:Temp -ChildPath MyScript.ps1
$sb = [scriptblock]::create(".{$(Get-Content -Path $pathToScript -Raw)} $(&{
$args
} #params)")
Invoke-Command -ScriptBlock $sb
I have used this in lots of scenarios and it works really well.
One thing that you occasionally need to do is put quotes around the parameter value assignment block. This is always the case when there are spaces in the value.
e.g. This param block is used to call a script that copies various modules into the standard location used by PowerShell C:\Program Files\WindowsPowerShell\Modules which contains a space character.
$params = #{
SourcePath = "$WorkingDirectory\Modules"
DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
}
Hope this helps!
This is an unfortunate situation. Positional parameters work.
# test.ps1
param($myarg1, $myarg2, $myarg3)
"myarg1 $myarg1"
"myarg2 $myarg2"
"myarg3 $myarg3"
# elevated prompt
invoke-command localhost test.ps1 -args 1,$null,3
myarg1 1
myarg2
myarg3 3
Or you can hardcode a default.
# test2.ps1
param($myarg='foo2')
dir $myarg
invoke-command localhost test2.ps1
Cannot find path 'C:\Users\js\Documents\foo2' because it does not exist.
Or copy the script there:
$s = New-PSSession localhost
copy-item test2.ps1 $home\documents -ToSession $s
icm $s { .\test2.ps1 -myarg foo3 }
Cannot find path 'C:\Users\js\Documents\foo3' because it does not exist.
I have a script that I can run remotely via Invoke-Command
Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
-FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1
As long as I use default parameters, it works fine. However, the script has 2 named [switch] parameters (-Debug and -Clear)
How can I pass the switched parameters via the Invoke-Command? I've tried the -ArgumentList but I'm getting errors so I must have the syntax wrong or something. Any help is greatly appreciated.
-ArgumentList is based on use with scriptblock commands, like:
Invoke-Command -Cn (gc Servers.txt) {param($Debug=$False, $Clear=$False) C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 } -ArgumentList $False,$True
When you call it with a -File it still passes the parameters like a dumb splatted array. I've submitted a feature request to have that added to the command (please vote that up).
So, you have two options:
If you have a script that looked like this, in a network location accessible from the remote machine (note that -Debug is implied because when I use the Parameter attribute, the script gets CmdletBinding implicitly, and thus, all of the common parameters):
param(
[Parameter(Position=0)]
$one
,
[Parameter(Position=1)]
$two
,
[Parameter()]
[Switch]$Clear
)
"The test is for '$one' and '$two' ... and we $(if($DebugPreference -ne 'SilentlyContinue'){"will"}else{"won't"}) run in debug mode, and we $(if($Clear){"will"}else{"won't"}) clear the logs after."
Without getting hung up on the meaning of $Clear ... if you wanted to invoke that you could use either of the following Invoke-Command syntaxes:
icm -cn (gc Servers.txt) {
param($one,$two,$Debug=$False,$Clear=$False)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 #PSBoundParameters
} -ArgumentList "uno", "dos", $false, $true
In that one, I'm duplicating ALL the parameters I care about in the scriptblock so I can pass values. If I can hard-code them (which is what I actually did), there's no need to do that and use PSBoundParameters, I can just pass the ones I need to. In the second example below I'm going to pass the $Clear one, just to demonstrate how to pass switch parameters:
icm -cn $Env:ComputerName {
param([bool]$Clear)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $(Test-Path $Profile)
The other option
If the script is on your local machine, and you don't want to change the parameters to be positional, or you want to specify parameters that are common parameters (so you can't control them) you will want to get the content of that script and embed it in your scriptblock:
$script = [scriptblock]::create( #"
param(`$one,`$two,`$Debug=`$False,`$Clear=`$False)
&{ $(Get-Content C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -delimiter ([char]0)) } #PSBoundParameters
"# )
Invoke-Command -Script $script -Args "uno", "dos", $false, $true
PostScript:
If you really need to pass in a variable for the script name, what you'd do will depend on whether the variable is defined locally or remotely. In general, if you have a variable $Script or an environment variable $Env:Script with the name of a script, you can execute it with the call operator (&): &$Script or &$Env:Script
If it's an environment variable that's already defined on the remote computer, that's all there is to it. If it's a local variable, then you'll have to pass it to the remote script block:
Invoke-Command -cn $Env:ComputerName {
param([String]$Script, [bool]$Clear)
& $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)
My solution to this was to write the script block dynamically with [scriptblock]:Create:
# Or build a complex local script with MARKERS here, and do substitutions
# I was sending install scripts to the remote along with MSI packages
# ...for things like Backup and AV protection etc.
$p1 = "good stuff"; $p2 = "better stuff"; $p3 = "best stuff"; $etc = "!"
$script = [scriptblock]::Create("MyScriptOnRemoteServer.ps1 $p1 $p2 $etc")
#strings get interpolated/expanded while a direct scriptblock does not
# the $parms are now expanded in the script block itself
# ...so just call it:
$result = invoke-command $computer -script $script
Passing arguments was very frustrating, trying various methods, e.g.,
-arguments, $using:p1, etc. and this just worked as desired with no problems.
Since I control the contents and variable expansion of the string which creates the [scriptblock] (or script file) this way, there is no real issue with the "invoke-command" incantation.
(It shouldn't be that hard. :) )
I suspect its a new feature since this post was created - pass parameters to the script block using $Using:var. Then its a simple mater to pass parameters provided the script is already on the machine or in a known network location relative to the machine
Taking the main example it would be:
icm -cn $Env:ComputerName {
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}
I needed something to call scripts with named parameters. We have a policy of not using ordinal positioning of parameters and requiring the parameter name.
My approach is similar to the ones above but gets the content of the script file that you want to call and sends a parameter block containing the parameters and values.
One of the advantages of this is that you can optionally choose which parameters to send to the script file allowing for non-mandatory parameters with defaults.
Assuming there is a script called "MyScript.ps1" in the temporary path that has the following parameter block:
[CmdletBinding(PositionalBinding = $False)]
param
(
[Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
[Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
[Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)
This is how I would call this script from another script:
$params = #{
MyNamedParameter1 = $SomeValue
MyNamedParameter2 = $SomeOtherValue
}
If ($SomeCondition)
{
$params['MyNamedParameter3'] = $YetAnotherValue
}
$pathToScript = Join-Path -Path $env:Temp -ChildPath MyScript.ps1
$sb = [scriptblock]::create(".{$(Get-Content -Path $pathToScript -Raw)} $(&{
$args
} #params)")
Invoke-Command -ScriptBlock $sb
I have used this in lots of scenarios and it works really well.
One thing that you occasionally need to do is put quotes around the parameter value assignment block. This is always the case when there are spaces in the value.
e.g. This param block is used to call a script that copies various modules into the standard location used by PowerShell C:\Program Files\WindowsPowerShell\Modules which contains a space character.
$params = #{
SourcePath = "$WorkingDirectory\Modules"
DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
}
Hope this helps!
This is an unfortunate situation. Positional parameters work.
# test.ps1
param($myarg1, $myarg2, $myarg3)
"myarg1 $myarg1"
"myarg2 $myarg2"
"myarg3 $myarg3"
# elevated prompt
invoke-command localhost test.ps1 -args 1,$null,3
myarg1 1
myarg2
myarg3 3
Or you can hardcode a default.
# test2.ps1
param($myarg='foo2')
dir $myarg
invoke-command localhost test2.ps1
Cannot find path 'C:\Users\js\Documents\foo2' because it does not exist.
Or copy the script there:
$s = New-PSSession localhost
copy-item test2.ps1 $home\documents -ToSession $s
icm $s { .\test2.ps1 -myarg foo3 }
Cannot find path 'C:\Users\js\Documents\foo3' because it does not exist.
I have two server one of them is Active directory and other is Windows10.
I want to write a Powershell script that delete and make a directory.(for easy situation I choose delete and make directory but in real, The script used for getting group and organization unit of active directory.)
I write three function and below Powershell script. the first one is for run command on remote server and two of them for create and delete a directory. The my_powershell_script.ps1
Param($choosen_function, $username, $password, $remote_address, $absolute_path)
function run_commend_on_remote_server {
param ($choosen_function, $username, $password, $remote_address)
$secpsw = $password | ConvertTo-SecureString -AsPlainText -Force
$credobject = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $secpsw
$psrs = New-PSSession -Credential $credobject -ComputerName $remote_address
Enter-PSSession $psrs
Invoke-Command -ScriptBlock {$choosen_function -absolute_path $absolute_path} -Session $psrs
Exit-PSSession
Remove-PSSession $psrs
}
function delete_directory{
param($absolute_path)
Remove-Item $absolute_path -Recurse
}
function make_directory{
param($absolute_path)
mkdir $absolute_path
}
run_commend_on_remote_server -choosen_function $choosen_function -username $username -password $password -remote_address $remote_address -absolute_path $absolute_path
When I run this script as below I got errors:
my_powershell_script.ps1 -choosen_function make_directory -username admin -password admin -remote_address 192.168.2.22
There are a few things which need to be changed in order for this script to work.
On your run_commend_on_remote_server function, you're using Enter-PSSession which is meant for interactive sessions, and then you're using Invoke-Command, in this case if I understand your intent correctly Enter-PSSession has to be removed.
On the same function, inside Invoke-Command script blocks's, $choosen_function is being used however the 2 helper functions have not been defined on that scope (remote scope). You need to pass the definition of your functions if you want to use them remotely. Same thing applies for $absolute_path. In order to pass locally defined variables into the remote scope, you can use either -ArgumentList or $using:, see Example 9 from the Invoke-Command Doc.
The command uses the Using scope modifier to identify a local variable in a remote command. By default, all variables are assumed to be defined in the remote session. The Using scope modifier was introduced in PowerShell 3.0.
Since the name of the helper function you want to run is stored in a variable, in order to execute it, you would need to use the call operator & or the dot sourcing operator . to invoke it, i.e.: & $choosen_function.
Instead of passing UserName and Password as argument of your script, I would personally recommend you to call Get-Credential inside your script.
-absolute_path is being used as parameter for run_commend_on_remote_server but the function does not have such parameter.
With these points being understood, here is how you could approach your script:
[cmdletbinding()]
param(
[ValidateSet('delete_directory', 'make_directory')]
$choosen_function,
$remote_address,
$absolute_path
)
function delete_directory{
# code here
}
function make_directory{
# code here
}
# store both function's definition, this will be used
# to pass them to the remote scope
$def = #(
${function:delete_directory}.ToString()
${function:make_directory}.ToString()
)
function run_commend_on_remote_server {
param ($choosen_function, $remote_address, $Credential, $absolute_path)
Invoke-Command -ScriptBlock {
# bring the definition of both functions to this scope
$deldir, $makedir = $using:def
# define both functions
${function:delete_directory} = $deldir
${function:make_directory} = $makedir
# invoke the chosen function
& $using:choosen_function -absolute_path $using:absolute_path
} -Credential $Credential -ComputerName $remote_address
}
# Here we call Get-Credential to get the pop-up for username and secure password
$cred = Get-Credential
$params = #{
choosen_function = $choosen_function
credential = $cred
remote_address = $remote_address
absolute_path = $absolute_path
}
run_commend_on_remote_server #params
Okay, so I've got a bit of code which calls another PS script from within a PS script, and passes in a couple of parameters: Invoke-Command -ScriptBlock { param($script,$a1,$a2) &$script #($a1,$a2) } -ArgumentList #($scriptToRun,$p1,$p2) -ComputerName localhost -Credential $cred
The problem I'm experiencing however, is the receiving script is getting both $a1 & $a2 combined together in $args[0]. What I can't figure out as yet, is how do I split out the two array elements again?
Alternatively, how can I get them to pass correctly without the #()?
Splatting isn't required here, so simply remove the #():
Invoke-Command -ScriptBlock {
param($script,$a1,$a2)
& $script $a1 $a2
} -ArgumentList $scriptToRun,$p1,$p2 ...
The default behavior is to assign each argument from the argument list to the corresponding positional parameter in the parameter definition of the script block.
Or, if you want to use splatting, you need to do it like this:
Invoke-Command -ScriptBlock {
param($script,$params)
& $script #params
} -ArgumentList $scriptToRun,#($p1,$p2) ...
I have a script that I can run remotely via Invoke-Command
Invoke-Command -ComputerName (Get-Content C:\Scripts\Servers.txt) `
-FilePath C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1
As long as I use default parameters, it works fine. However, the script has 2 named [switch] parameters (-Debug and -Clear)
How can I pass the switched parameters via the Invoke-Command? I've tried the -ArgumentList but I'm getting errors so I must have the syntax wrong or something. Any help is greatly appreciated.
-ArgumentList is based on use with scriptblock commands, like:
Invoke-Command -Cn (gc Servers.txt) {param($Debug=$False, $Clear=$False) C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 } -ArgumentList $False,$True
When you call it with a -File it still passes the parameters like a dumb splatted array. I've submitted a feature request to have that added to the command (please vote that up).
So, you have two options:
If you have a script that looked like this, in a network location accessible from the remote machine (note that -Debug is implied because when I use the Parameter attribute, the script gets CmdletBinding implicitly, and thus, all of the common parameters):
param(
[Parameter(Position=0)]
$one
,
[Parameter(Position=1)]
$two
,
[Parameter()]
[Switch]$Clear
)
"The test is for '$one' and '$two' ... and we $(if($DebugPreference -ne 'SilentlyContinue'){"will"}else{"won't"}) run in debug mode, and we $(if($Clear){"will"}else{"won't"}) clear the logs after."
Without getting hung up on the meaning of $Clear ... if you wanted to invoke that you could use either of the following Invoke-Command syntaxes:
icm -cn (gc Servers.txt) {
param($one,$two,$Debug=$False,$Clear=$False)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 #PSBoundParameters
} -ArgumentList "uno", "dos", $false, $true
In that one, I'm duplicating ALL the parameters I care about in the scriptblock so I can pass values. If I can hard-code them (which is what I actually did), there's no need to do that and use PSBoundParameters, I can just pass the ones I need to. In the second example below I'm going to pass the $Clear one, just to demonstrate how to pass switch parameters:
icm -cn $Env:ComputerName {
param([bool]$Clear)
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $(Test-Path $Profile)
The other option
If the script is on your local machine, and you don't want to change the parameters to be positional, or you want to specify parameters that are common parameters (so you can't control them) you will want to get the content of that script and embed it in your scriptblock:
$script = [scriptblock]::create( #"
param(`$one,`$two,`$Debug=`$False,`$Clear=`$False)
&{ $(Get-Content C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -delimiter ([char]0)) } #PSBoundParameters
"# )
Invoke-Command -Script $script -Args "uno", "dos", $false, $true
PostScript:
If you really need to pass in a variable for the script name, what you'd do will depend on whether the variable is defined locally or remotely. In general, if you have a variable $Script or an environment variable $Env:Script with the name of a script, you can execute it with the call operator (&): &$Script or &$Env:Script
If it's an environment variable that's already defined on the remote computer, that's all there is to it. If it's a local variable, then you'll have to pass it to the remote script block:
Invoke-Command -cn $Env:ComputerName {
param([String]$Script, [bool]$Clear)
& $ScriptPath "uno" "dos" -Debug -Clear:$Clear
} -ArgumentList $ScriptPath, (Test-Path $Profile)
My solution to this was to write the script block dynamically with [scriptblock]:Create:
# Or build a complex local script with MARKERS here, and do substitutions
# I was sending install scripts to the remote along with MSI packages
# ...for things like Backup and AV protection etc.
$p1 = "good stuff"; $p2 = "better stuff"; $p3 = "best stuff"; $etc = "!"
$script = [scriptblock]::Create("MyScriptOnRemoteServer.ps1 $p1 $p2 $etc")
#strings get interpolated/expanded while a direct scriptblock does not
# the $parms are now expanded in the script block itself
# ...so just call it:
$result = invoke-command $computer -script $script
Passing arguments was very frustrating, trying various methods, e.g.,
-arguments, $using:p1, etc. and this just worked as desired with no problems.
Since I control the contents and variable expansion of the string which creates the [scriptblock] (or script file) this way, there is no real issue with the "invoke-command" incantation.
(It shouldn't be that hard. :) )
I suspect its a new feature since this post was created - pass parameters to the script block using $Using:var. Then its a simple mater to pass parameters provided the script is already on the machine or in a known network location relative to the machine
Taking the main example it would be:
icm -cn $Env:ComputerName {
C:\Scripts\ArchiveEventLogs\ver5\ArchiveEventLogs.ps1 -one "uno" -two "dos" -Debug -Clear $Using:Clear
}
I needed something to call scripts with named parameters. We have a policy of not using ordinal positioning of parameters and requiring the parameter name.
My approach is similar to the ones above but gets the content of the script file that you want to call and sends a parameter block containing the parameters and values.
One of the advantages of this is that you can optionally choose which parameters to send to the script file allowing for non-mandatory parameters with defaults.
Assuming there is a script called "MyScript.ps1" in the temporary path that has the following parameter block:
[CmdletBinding(PositionalBinding = $False)]
param
(
[Parameter(Mandatory = $True)] [String] $MyNamedParameter1,
[Parameter(Mandatory = $True)] [String] $MyNamedParameter2,
[Parameter(Mandatory = $False)] [String] $MyNamedParameter3 = "some default value"
)
This is how I would call this script from another script:
$params = #{
MyNamedParameter1 = $SomeValue
MyNamedParameter2 = $SomeOtherValue
}
If ($SomeCondition)
{
$params['MyNamedParameter3'] = $YetAnotherValue
}
$pathToScript = Join-Path -Path $env:Temp -ChildPath MyScript.ps1
$sb = [scriptblock]::create(".{$(Get-Content -Path $pathToScript -Raw)} $(&{
$args
} #params)")
Invoke-Command -ScriptBlock $sb
I have used this in lots of scenarios and it works really well.
One thing that you occasionally need to do is put quotes around the parameter value assignment block. This is always the case when there are spaces in the value.
e.g. This param block is used to call a script that copies various modules into the standard location used by PowerShell C:\Program Files\WindowsPowerShell\Modules which contains a space character.
$params = #{
SourcePath = "$WorkingDirectory\Modules"
DestinationPath = "'$(Join-Path -Path $([System.Environment]::GetFolderPath('ProgramFiles')) -ChildPath 'WindowsPowershell\Modules')'"
}
Hope this helps!
This is an unfortunate situation. Positional parameters work.
# test.ps1
param($myarg1, $myarg2, $myarg3)
"myarg1 $myarg1"
"myarg2 $myarg2"
"myarg3 $myarg3"
# elevated prompt
invoke-command localhost test.ps1 -args 1,$null,3
myarg1 1
myarg2
myarg3 3
Or you can hardcode a default.
# test2.ps1
param($myarg='foo2')
dir $myarg
invoke-command localhost test2.ps1
Cannot find path 'C:\Users\js\Documents\foo2' because it does not exist.
Or copy the script there:
$s = New-PSSession localhost
copy-item test2.ps1 $home\documents -ToSession $s
icm $s { .\test2.ps1 -myarg foo3 }
Cannot find path 'C:\Users\js\Documents\foo3' because it does not exist.