I'm writing a parameterized function and trying to use splatting to reuse the parameter-set.
$Computername='localhost'
$params = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #params
}
On executing like this it fails doesn't recognize the user-inputed servername. It still takes the value of $Computername initially specified.
Get-Diskinfo -drive c: Server1 administrator
How can I get it to identify the user-inputed computername? Am I missing something ?
Instead of splatting random hashtable, you need to splat something that is defined automatically by PowerShell:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
# Need to remove any parameter that is used "internally" and don't exist on cmdlet
$PSBoundParameters.Remove('drive') | Out-Null
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #PSBoundParameters
}
Get-Diskinfo -drive c: Server1 administrator
Alternatively: use passed parameters to build $parms inside your function:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
$parms = #{
computername = $Computername
credential = $credential
}
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #parms
}
You aren't calling #parms until inside your function.
This should work, using the parameters within your function to pull the WMI query:
$Computername='localhost'
$parms = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" -ComputerName $computername -Credential $credential
}
And then use the splatting to run the function:
Get-Diskinfo -drive c: #parm
The function can still be run using the parameters directly:
Get-Diskinfo -drive c: Server1 administrator
Your $params hashtable gets it's value when you create it, not when it's used. Your $computername,$credential parameters are never referenced inside your function, which means that they are dead parameters.
You can't use splatting and normal function-calling with parameters for the same parameter at the same time. You should only use parameters inside your function scriptblock. Splatting is for the end-user only. #Tim Ferril has shown you how to write the function properly.
If you need to be able to use both default values AND be able to change ex. ComputerName for one cmdlet, then you should use default values and not splatting. Ex:
function Test-Global {
param(
$ComputerName = $global:PC,
$Username = $global:username
)
"Computername is '$ComputerName'"
"Username is '$UserName'"
}
#Set the reusable variables in global scope and call your function
$global:Username = "Frode"
$global:PC = "MyPC"
PS C:\Users\Frode> Test-Global
Computername is 'MyPC'
Username is 'Frode'
#If I need to modify the server for one function, I'd simply specify that parameter
PS C:\Users\Frode> Test-Global -ComputerName server1
Computername is 'server1'
Username is 'Frode'
To make it easier for user to set the default values, you could create a function for that too.
#Create your reusable parameters
function Set-Params {
param(
$ComputerName,
$Username
)
if($Username) { $global:Username = $Username }
if($ComputerName) { $global:PC = $ComputerName }
}
PS C:\Users\Frode> Set-Params -ComputerName Test1 -Username Test1
PS C:\Users\Frode> Test-Global
Computername is 'Test1'
Username is 'Test1'
PS C:\Users\Frode> Set-Params -ComputerName Test2
PS C:\Users\Frode> Test-Global
Computername is 'Test2'
Username is 'Test1'
Related
I need to work on a fairly complicated script that involves multiple service accounts to different APIs and for the most part, the script works fine but I'm running into an issue that's bugging me like crazy. I can't run the script as a different user because the powershell running the node can only run the script as NT Authority.
So a part of my script looks like this:
Foreach ($i in $AllUsers) {
$ConID = (Get-ADUser -Identity $i -Properties ExtensionAttribute2 -Credential $svcPOSHCreds).ExtensionAttribute2
$ConDN = (get-aduser -filter {EmployeeID -eq $ManagerEID} -Credential $svcPOSHCreds).DistinguishedName
Set-ADUser -Identity $i -Manager $ConDN-Credential $svcPOSHCreds
Get-ADPrincipalGroupMembership $i -Credential $svcPOSHCreds | foreach {Remove-ADGroupMember $_ -Members $i - Confirm:$false -Credential $svcPOSHCreds}
Add-ADGroupMember -Identity 'Identity Con User' -Members $i -Credential $svcPOSHCreds
}
As you can see, I have to specify -Credential for every command I run because the user running this script doesn't have the rights to do some of the stuff.
So far I can count roughly 108 "-Credential" parameters to all of the commands between different APIs and AD...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command? I cannot specify how the script will be ran :( My limit is only to the inside of the PS1! so no "runas"...etc.
Is there a way that I can group the commands together so they utilize the same "Credentials" so I don't have to specify it each time when I run each command?
Yes, you can use the preference variable $PSDefaultParameterValues to automate this process.
Here is a simple example to demonstrate how it works, first we can define to test functions:
function Set-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
function Get-Something {
[CmdletBinding()]
param([pscredential] $Credential, [string] $SomeParam)
[pscustomobject]#{
SomeParam = $SomeParam
UserName = $Credential.UserName
Password = [Net.NetworkCredential]::new('', $Credential.Password).Password
}
}
And for these 2 functions we will set their -Credential parameter as a Default Parameter:
$cred = [pscredential]::new('hello', (ConvertTo-SecureString 'world' -AsPlainText))
$PSDefaultParameterValues = #{
'Set-Something:Credential' = $cred
'Get-Something:Credential' = $cred
}
Then we can test if it works correctly, here we can assume that the $cred variable will be passed as default to both functions:
Get-Something -SomeParam 123
Set-Something -SomeParam 123
The result is:
SomeParam UserName Password
--------- -------- --------
123 hello world
123 hello world
You can also use wildcards here, so for example:
$PSDefaultParameterValues = #{
'*-Something:Credential' = $cred
}
Would target all functions with noun Something.
Quick one? - As I'm still learning Powershell. I was wondering if it was possible to combine parameters inside a script with parameters entered on the command line?
i.e. I have a function like this as an example...
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
$SiteName = "London1"
$Subnet= "192.168.10.1/24"
$Cred = <supplied>
#$ComputerName = "blah1"
GetInfo $SiteName $Subnet $Cred $ComputerName
$SiteName = "London2"
$Subnet= "192.168.11.1/24"
$Cred = <supplied>
#$ComputerName = "blah2"
GetInfo $SiteName $Subnet $Cred $ComputerName
Now say inside the script I would specify the SiteName, Subnet, Cred.... but on the command line I would like to specify -ComputerName
But as I'm using the script & let's say I know that Lon1-PC1 is in "London1" I would like to do this on the calling command:
.\GetPCInf.ps1 -ComputerName "Lon1-PC1"
or
.\GetPCInf.ps1 -ComputerName "Lon2-PC1"
Obviously there will be additional logic inside the script to say that if the -ComputerName prefix is "Lon1" then do X or if -ComputerName prefix is "Lon2" then do Y..
Obviously I know I can just put the computername in the script, save it & run it.
So far when I try, nothing happens in relation to the -Computername return..
I haven't yet tried combining a parameter & Args - but I've read Args is not the best to use so I'm trying to avoid it.
If this can't be done then fair enough, just wondered if someone might know if this can be done, as it saves me typing in:
.\GetPCInf.ps1 -SiteName London1 -Subnet "192.168.10.1/24" -Cred mycreds -ComputerName "Lon1-PC1"
each time I want to run it....
I suppose I could create batch files to call the script & put %1 for computername in the batch file & call it that way, but just curious really..
Many thanks.
So far when I try, nothing happens in relation to the -Computername return..
Scripts are just functions stored in files - and they support param blocks and parameter declarations just like functions - so declare a $ComputerName parameter:
# GetPCInf.ps1
param(
[Parameter(Mandatory = $true)]
[string]$ComputerName
)
# define GetInfo function
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
# define table of arguments to pass to GetInfo
$GetInfoArgs = #{
ComputerName = $ComputerName
}
# add additional arguments based on computer name value
if($ComputerName -like 'Lon1-*'){
$GetInfoArgs += #{
SiteName = "London1"
Subnet= "192.168.10.1/24"
Cred = $(<# credential goes here #>)
}
} elseif($ComputerName -like 'Lon2-*') {
$GetInfoArgs += #{
SiteName = "London2"
Subnet= "192.168.11.1/24"
Cred = $(<# credential goes here #>)
}
} else {
$GetInfoArgs += #{
SiteName = "DefaultSite"
Subnet= "192.168.12.1/24"
Cred = $(<# credential goes here #>)
}
}
# invoke GetInfo function
GetInfo #GetInfoArgs
I am writing a script in powershell where after login with User 1 on a system, it will switch to user 2 and then make a connection to database with this user. However, the dbinstance details, port No and Computer name to be passed in invoke command will be defined as a map before the 2nd invoke command i.e. when it will invoke the command to open powershell with 2nd user(db user). It is able to take userid in this case i.e. when to invoke the powershell connection with 2nd user, however it is not able to pass the values of dbinstance and port to next sqlcmd invoke. Below is the code for reference. In this code it works fine while getting $inputMap.UserNameP, however it fails in passing $inputMap.DBInstance,$inputMap.PortNo.
$UserName = 'User1'
$securekey = #'
securekey1
'# |ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $securekey;
Invoke-Command -Credential $concreds -ComputerName 'abc.domainname'-Authentication Credssp -ScriptBlock {
function checkFaultHighUtilization() {
$local:ExecStdOperatorOut=Invoke-Command -ScriptBlock {
$inputMap=#{"UserNameP"="User2";"DBInstance"="databaseinstancename";"PortNo"="portnumber";};
$securekey1 = "securekey1"
$finalresult = #()
$securekey2 = $securekey1 | ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $inputMap.UserNameP, $securekey2;
Invoke-Command -Credential $concreds -ComputerName 'computername' -Authentication Credssp -ScriptBlock {
$var1=Invoke-Sqlcmd -query "
Begin
select * from db
End" -ServerInstance "$inputMap.DBInstance,$inputMap.PortNo"
##if (($var1.count) -gt 0) {
foreach($row in $var1){
$finalresult+=$row.a+':'+$row.b+':'+$row.c
echo $finalresult
}
}
}
$local:ExecStdOperatorRet=if($local:ExecStdOperatorOut) {0} else {1}
return $local:ExecStdOperatorRet,$local:ExecStdOperatorOut;
};
$ESExecReturn,$ESExecOutput=checkFaultHighUtilization
$ESExecOutput=($ESExecOutput | Out-String).Trim();
Write-output "ESExecOutput:";
Write-output $ESExecOutput;
Write-output ":ESExecOutput";Write-output $("ESExecError:" + $Error + ":ESExecError");
Write-output $("ESExecReturn:" + $ESExecReturn + ":ESExecReturn");
}
$scriptBlockOne = {
$variableA = "Hello World"
return $variableA
}
$scriptBlockTwo = {
param (
$inputString
)
Write-host $inputString
}
$invokeCommandReturn = Invoke-Command -ScriptBlock $scriptBlockOne
Invoke-Command -ScriptBlock $scriptBlockTwo -ArgumentList $invokeCommandReturn
You're trying to use expressions such as $inputMap.DBInstance as-is inside an expandable string ("..."), which is syntactically not supported.
To use expressions, you must enclose them in $(...), the subexpression operator.
See this answer for a comprehensive discussion of string interpolation in PowerShell.
Therefore:
# ...
$var1 = Invoke-Sqlcmd -Query "
Begin
select * from db
End" -ServerInstance "$($inputMap.DBInstance),$($inputMap.PortNo)" # Note the $()
# ...
I remotely try to set the DNS server addresses by, passing the Invoke-Command a string array of DNS server IP addresses as an argument.
This code only sets the first address (10.1.1.2)
$dnsIPCollection = #("10.1.1.2", "10.1.1.2")
Invoke-Command -ComputerName $serverNameHere -ScriptBlock { param($dnsIPCollection) Get-DnsClientServerAddress -InterfaceIndex 12 -AddressFamily IPv4 | Set-DnsClientServerAddress -ServerAddresses $dnsIPCollection } -Credential $creds -ArgumentList $dnsIPCollection
Now, if I try the following code, It sets both IP addresses
Invoke-Command -ComputerName $serverNameHere -ScriptBlock { Get-DnsClientServerAddress -InterfaceIndex 12 -AddressFamily IPv4 | Set-DnsClientServerAddress -ServerAddresses ("10.1.1.2", "10.1.1.2") } -Credential $creds
If I execute the following code from the remote server, it also sets both IP addresses
$dnsIPCollection = #("10.1.1.2", "10.1.1.2")
Set-DnsClientServerAddress -ServerAddresses $dnsIPCollection
I'm not sure if this is a bug or I'm doing something wrong. According to the ServerAddresses parameter documentation, the data type is String[].
Is anyone able to share some insight on my observations?
My $PSVersionTable output:
The problem is how you're passing your -ArgumentList $dnsIPCollection to Invoke-Command - you're expecting it to pass the whole array as a single argument, but PowerShell is exploding it out into a list of arguments.
It's basically doing this:
PS> $myArgs = #("10.1.1.x", "10.1.1.y")
PS> Invoke-Command -ScriptBlock { param( $p1, $p2 )
write-host "p1 = '$p1'"
write-host "p2 = '$p2'"
} -ArgumentList $myArgs
which gives the output
p1 = '10.1.1.x'
p2 = '10.1.1.y'
Except that your script block only declares one parameter so it only receives the first value in the array of arguments - you're effectively only passing $p1 into Set-DnsClientServerAddress.
If you want your array to be passed into your script block as a single argument then you need to wrap it in another array so that your array becomes $p1 when PowerShell explodes the -ArgumentList:
$myArgs = #("10.1.1.x", "10.1.1.y")
Invoke-Command -ScriptBlock { param( $p1 )
write-host "p1 = '$p1'"
} -ArgumentList #(, $myArgs )
This now outputs:
p1 = '10.1.1.x 10.1.1.y'
Hopefully that helps - I'll leave applying this back into your original question as an exercise for the reader, but leave a comment if you get stuck doing it :-).
Update
Just to labour the point, the reason PowerShell explodes the argument list might be more obvious with an example with meaningful variable names:
PS> Invoke-Command -ScriptBlock { param( $username, $password )
write-host "username = '$username'"
write-host "password = '$password'"
} -ArgumentList #( "myUsername", "myPassword" )
Your example where only one IP address is being set is pretty much the same as this:
PS> Invoke-Command -ScriptBlock { param( $username )
write-host "username = '$username'"
} -ArgumentList #( "myUsername", "myPassword" )
And then it should be easy to see why your second IP address isn't being set - your array of IP addresses is being treated as a list of arguments rather than a single array argument...
How is it possible to use the parameters collected in a hash table for use with ArgumentList on Invoke-Command?
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
Structure = 'yyyy-MM-dd'
}
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock ${Function:Copy-FilesHC} -ArgumentList #CopyParams
Whatever I try, it's always complaining about the 'Source':
Cannot validate argument on parameter 'Source'. The "Test-Path $_" validation script for the argument with
value "System.Collections.Hashtable" did not return true. Determine why the validation script failed
This blog talks about a similar problem, but I can't get it to work.
The same is true for a simple Copy-Item within Invoke-Command, example:
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {Copy-Item} -ArgumentList #CopyParams
Invoke-Command : Missing an argument for parameter 'ArgumentList'. Specify a parameter of type 'System.Obj
ect[]' and try again.
At line:11 char:89
+ ... ck {Copy-Item} -ArgumentList #CopyParams
Thank you for your help.
One-liner, to convert a remote script to accept named parameters from a hash.
Given a scriptblock which you wish to call like this:
$Options = #{
Parameter1 = "foo"
Parameter2 = "bar"
}
Invoke-Command -ComputerName REMOTESERVER -ArgumentList $Options -ScriptBlock {
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
}
You can convert it like so
Invoke-Command -Computername REMOTESERVER -ArgumentList $Options -ScriptBlock {param($Options)&{
param(
$Parameter1,
$Parameter2
)
#Script goes here, this is just a sample
"ComputerName: $ENV:COMPUTERNAME"
"Parameter1: $Parameter1"
"Parameter2: $Parameter2"
} #Options}
What's going on? Essentially we've wrapped the original script block like so:
{param($Options)& <# Original script block (including {} braces)#> #options }
This makes the original script block an anonymous function, and creates the outer script block which has a parameter $Options, which does nothing but call the inner script block, passing #options to splat the hash.
Here's one way to approach passing named parameters:
function Copy-FilesHC
{
param ($Source,$Destination,$Structure)
"Source is $Source"
"Desintation is $Destination"
"Structure is $Structure"
}
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
Destination = "'E:\DEPARTMENTS\CBR\SHARE\Target 2'" #Nested quotes required due to embedded space in value.
Structure = 'yyyy-MM-dd'
}
$SB = [scriptblock]::Create(".{${Function:Copy-FilesHC}} $(&{$args}#CopyParams)")
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock $SB
Basically, you create a new script block from your invoked script, with the parameters splatted to that from the hash table. Everything is already in the script block with the values expanded, so there's no argument list to pass.
I found a workaround, but you have to make sure that your Advanced function which is located in your module file is loaded up front in the local session. So it can be used in the remote session. I wrote a small helper function for this.
Function Add-FunctionHC {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[String]$Name
)
Process {
Try {
$Module = (Get-Command $Name -EA Stop).ModuleName
}
Catch {
Write-Error "Add-FunctionHC: Function '$Name' doesn't exist in any module"
$Global:Error.RemoveAt('1')
Break
}
if (-not (Get-Module -Name $Module)) {
Import-Module -Name $Module
}
}
}
# Load funtion for remoting
Add-FunctionHC -Name 'Copy-FilesHC'
$CopyParams = #{
Source = 'E:\DEPARTMENTS\CBR\SHARE\Target\De file.txt'
Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
}
$RemoteFunctions = "function Copy-FilesHC {${function:Copy-FilesHC}}" #';' seperated to add more
Invoke-Command -ArgumentList $RemoteFunctions -ComputerName 'SERVER' -Credential $Cred -ScriptBlock {
Param (
$RemoteFunctions
)
. ([ScriptBlock]::Create($RemoteFunctions))
$CopyParams = $using:CopyParams
Copy-FilesHC #CopyParams
}
The big advantage is that you don't need to copy your complete function in the script and it can stay in the module. So when you change something in the module to the function it will also be available in the remote session, without the need to update your script.
I recently experienced a similar problem and solved it by building the hash (or rebuilding the hash) inside the invoke by leveraging the $using variable scope (more on that here)
it looks something like this:
$Source = 'E:\DEPARTMENTS\CBR\SHARE\Target'
$Destination = 'E:\DEPARTMENTS\CBR\SHARE\Target 2'
$Structure = 'yyyy-MM-dd'
Invoke-Command -Credential $Cred -ComputerName 'SERVER' -ScriptBlock {
$CopyParms= #{
'Source'=$Using:Source
'Destination'=$Using:Destination
'Structure'=$Using:Structure
}
Function:Copy-FilesHC #CopyParms
}
This is what works for me:
$hash = #{
PARAM1="meaning of life"
PARAM2=42
PARAM3=$true
}
$params = foreach($x in $hash.GetEnumerator()) {"$($x.Name)=""$($x.Value)"""}
I know this is late, but I ran into the same problem and found a solution that worked for me. Assigning it to a variable within the scriptblock and then using that variable to splat didn't show any problems.
Here's an example:
$param=#{"parameter","value"}
invoke-command -asjob -session $session -ScriptBlock {$a=$args[0];cmdlet #a } -ArgumentList $param