Call function with arguments in -ScriptBlock of Invoke-Command Powershell command - powershell

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

Related

JEA Invoke Command Issue

I have been working on implementing JEA into my environment to allow for our Jenkins System to not have local admin controls on all of our servers.
I have been able to create the JEA files, and specified commands that the user account can run.
The problem i am running into is that i am needing to pass a variable value from my Jenkins server to a remote server. So far i am not able to perform this.
Here is the code i am using with Powershell:
$session = New-PSSession -ComputerName "webaoneratebook" -ConfigurationName JEA-A1
$user = "USER"
$File = "C:\users\svc.jenkins.prd\Documents\WindowsPowerShell\FILE.txt"
$myCredentials = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user ,(Get-Content $file| ConvertTo-SecureString)
$pass = $MyCredentials.GetNetworkCredential().Password
Start-sleep -s 3
Invoke-Command -Session $session -scriptblock {AnalyzeRatebook} -ArgumentList $pass
Get-PSSession| Remove-PSSession
No matter what i have tried, i cannot seem to get the password to pass over.
In the JEA PSRC file, i have set the following remote variables:
VariableDefinitions = #{ Name = 'log'; Value = 'D:\inetpub\wwwroot\Jenkinstemp\A1Log.log'},
#{ Name = 'binpath'; Value = 'D:\inetpub\wwwroot\AscendantOne\bin' },
#{ Name = 'user'; Value = 'USER' },
#{ Name = 'pass2'; Value = 'TEMP' },
#{ Name = 'manifest'; Value = 'D:\inetpub\wwwroot\Jenkinstemp\manifest.xml' }
In the JEA PSRC file, i have made my function like this:
FunctionDefinitions = #{
'Name' = 'AnalyzeRatebook'
'ScriptBlock' = {
& $binpath\AOImportCmd.exe -u $user -p $pass -o $log -a $manifest
}
}
And made sure it is in Visible Functions as well.
VisibleFunctions = 'AnalyzeRatebook','TestRatebook',#{ Name = 'AnalyzeRatebook'; Parameters = #{ Name = 'ArgumentList'}}
If i do a Write-Host "& $binpath\AOImportCmd.exe -u $user -p $pass -o $log -a $manifest", I get back everything except the password.
So can anyone give some advice on how to get the local variable to translate over to a remote system when using JEA?
More Secure Resolution:
I worked a bit more and found a better answer that preserves the NoLanguage Mode of JEA. This is useful since ConstrainedLanguage Mode and FullLanguage Mode give too much power for modification even in JEA limited accounts.
In the end, i had to add two additional cmdlets to the VisibleCmdlets listing.
Get-Variable and Set-Variable.
Once i added these, I was able to set the system back to NoLanguage mode, and then pass my local variables to the remote host via the ICM command below:
Invoke-Command -Session $session -scriptblock {AnalyzeRatebook -u"$Using:user" -p"$Using:pass" -o"$Using:log" $Using:manifest}
Alternative Resolution
The problem was due to Three (edit: math is hard) reasons:
1. No-Language Mode was configured. This is modified in the PSSC file.
2. User error with how i defined Param in the PSRC file.
3. ICM needed to be changed in how it reached out.
Item 1:
To resolve, i went into the JEA-A1.PSSC file on the remote system, then (in this case, there was never a language mode defined) added a new line to the file called LanguageMode.
LanguageMode = 'FullLanguage'
Item 2
Then i updated the Custom Function that i wrote in the JEA-A1.PSRCfile with the correct syntax.
Original:
# Functions to define when applied to a session
FunctionDefinitions = #{
'Name' = 'AnalyzeRatebook'
'ScriptBlock' = {
Param($user),($pass),($log),($manifest)
& $binpath\AOImportCmd.exe "-a" $user $pass $log $manifest
$LASTEXITCODE
}
}
Corrected:
# Functions to define when applied to a session
FunctionDefinitions = #{
'Name' = 'AnalyzeRatebook'
'ScriptBlock' = {
Param($user,$pass,$log,$manifest)
& $binpath\AOImportCmd.exe "-a" $user $pass $log $manifest
$LASTEXITCODE
}
}
Item 3:
Finally the Invoke-Command call needed to be updated to accurately read and parse the variables. In my case, I could not set them to a single string as the system was expecting a pause in between the values to signify what they were.
Invoke-Command -Session $session -scriptblock {Param($user,$pass,$log,$manifest) AnalyzeRatebook -u"$user" -p"$pass" -o"$log" $manifest} -ArgumentList $user,$pass,$log,$manifest
Now this is working and passing the values correctly.

Call a remote script from another with multiple parameters not working

I am trying to create a script that will take input (hardcoded values for now) and call an install PS script and run it on multiple servers. I am using a PSSession and Invoke-Command(see below). The below runs, but does nothing. It doesn't seem to call the other script. Beyond getting it to actually install, I need to know if it was successful or not. I'm pretty novice at Powershell, so any hints/help/suggestions would be great. The below is wrapped in a ForEach to loop the servers with $Computer
Try
{
$session = New-PSSession -ComputerName App02 -Credential $cred
$sourceInstall = $sourceFolder + 'Install\Install.ps1'
Invoke-Command -Session $session -ScriptBlock{param($serviceName, $installFolder, $sourceFolder, $Action, $username, $password) $sourceInstall} -ArgumentList ($ServiceName, $installFolder, $sourceFolder, $Action, $username, $password)
}
Catch
{
$Filename = "Error.txt"
Write-Output "ERROR: Partial Service Deployment. See error log file(s)"
Add-Content $Filename $_.Exception.Message
}
Get-PSSession | Remove-PSSession
You can use it without $Using statement in any version of PowerShell.But pass that too as an argument.
Eg:-
Invoke-Command -ScriptBlock
param($Name)
& $Command $Name
} -ArgumentList 'Get-Process','Notepad'
But you have to pass the arguments positional when using the call operator '&'
Get-Help About_Parameters
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_parameters
Regards,
Kvprasoon

How do you define variables locally then reference them in a remote machine?

Let's say I have 3 functions; A, B and C. Obviously this is super watered down but the logic is there.
A is a function that gathers a name.
function A {
Set-Variable -Scope Script -Name "First" -Value (Read-Host "First name?")
Set-Variable -Scope Script -Name "Last" -Value (Read-Host "Last name?")
}
Function B will use this name to create a O365 user with an msol session. No big deal.
function B {
Connect-MsolService -Credential $creds
"Add user foo"
$Name = "$Script:First $Script:Last"
"Success!"
}
While function C uses the name from A but references it within a PSSession to a remote AD server. This is where my knowledge totally breaks down. I've tried everything I know to reference these local variables within C.
function C {
New-ADUser
$Name = "$Script:First $Script:Last"
}
New-PSSession -computername AD -credential $ADcreds
Invoke-Command -ComputerName AD -ScriptBlock ${function:C} -credential $ADcreds
Remove-PSSession $s0
But they are totally wiped. I've tried to invoke a script block first to kind of define them again using the old data with no luck. My $Script:Name always comes up null in the PSSession.
Am I missing something huge?
The using: variable prefix should work here. If not, you should define a Param section within your function and pass the arguments using the -Argument parameter to the Invoke-Command cmdlet.
$scriptBlockC = {
New-ADUser
$Name = "$using:First $using:Last"
}
Invoke-Command -ComputerName AD -ScriptBlock $scriptBlockC -credential $ADcreds
Note that I removed the function keyword since you are just defining a scriptblock.

PowerShell Splatting the Argumentlist on Invoke-Command

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

Include a PowerShell file inside a ScriptBlock

I have written the following code
$sb = {
. .\Myfunctions.ps1
$x = MyFunction1
$y = MyFunction2
$x + $y
}
$cred = Get-Credential "domain\user"
Invoke-Command -Computer localhost -Credentials $cred -ScriptBlock $sb
This does not work because it says The term .\MyFunctions.ps1 is not recognized as commandlet
Why can't I include a file inside a script block?
The problem is that the $pwd (current directory) in the script block is different from the actual console path casued this because you are using invoke-command with -computer parameter is like you are do it in a remoting session. Try to put full path to your script to call it or just use ( if locally) & $sb