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.
Related
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
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
The script mounts the drive correctly, but the drive is not persisted after rebooting the machine:
function RemapDrive {
param(
$DriveLetter,
$FullPath,
$Credential
)
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
# $DriveLetter must be concatenated with ":" for the command to work
net use "${DriveLetter}:" /del
## $DriveLetter cannot contain ":"
$psDrive = New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Credential $Credential -Scope "Global" -Persist
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X" -FullPath "\\my-server\x" -Credential $credential
What I have found:
“When you scope the command locally, that is, without dot-sourcing, the Persist parameter does not persist the creation of a PSDrive beyond the scope in which you run the command. If you run New-PSDrive inside a script, and you want the new drive to persist indefinitely, you must dot-source the script. For best results, to force a new drive to persist, specify Global as the value of the Scope parameter in addition to adding Persist to your command.”
I have tried executing the script with ". .\my-script.ps1" (to dot-source the script?), but the result is the same.
Playing around with "net use" and the registry to try to add the network drive has lead me to a cul-de-sac as well.
Specs:
Windows 10 Home
Powershell version:
Major Minor Build Revision
----- ----- ----- --------
5 1 18362 1171
Basically, New-PSDrive doesn't have the /SAVECRED parameter from net use, and will not persistently map drives as a user other than the one running the script.
There are three ways to handle this:
[Recommended] Fix the file share permissions instead of using a separate username/password, then use New-PSDrive -Name "$DriveLetter" -PSProvider "FileSystem" -Root "$FullPath" -Scope 'Global' -Persist with no credential flag. This assumes your file share allows kerberos logins, so may not work in some edge cases.
Use net use, and include the username, password, /persistent:yes and /savecred. This can be done in powershell without any issues.
Set the powershell script you already have to run at startup.
Set up your script to use the credential manager - see the answer here
Install the CredentialManager powershell module
set HKCU\Network\[drive letter]\ConnectionType = 1
set HKCU\Network\[drive letter]\DeferFlags= 4
What finally work was user19702's option #2, with a bit of extra work regarding the registration of the username and the password.
WARNING: as he mentioned, the best option (option #1) would have been "fixing the file share permissions instead of using a separate username/password". This was not possible in my case, and this is why I had to go with option #2.
This is the script:
# ---
# Helper functions:
function RemapDrive {
param(
$DriveLetter,
$Server,
$FullPath,
$Credential
)
# For net.exe to work, DriveLetter must end with with ":"
Write-Host "Trying to remove $DriveLetter in case it already exists ..."
net use "$DriveLetter" /del
# "net use" requires username and password as plain text
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
$Username=$Credential.Username
Write-Host "Registring credentials for server '$Server' ..."
cmdkey /add:$Server /user:$Username /pass:$Password
Write-Host "Mapping the drive ..."
net use $DriveLetter $FullPath /persistent:yes i
Write-Host "$DriveLetter was successfully added !"
}
function BuildCredential {
param (
$Username,
$Password
)
$pass = ConvertTo-SecureString $Password -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($Username, $pass)
return $credential
}
# ---
# Process to execute:
$credential = (BuildCredential -Username "xxxxxx" -Password "yyyyyy")[-1]
RemapDrive -DriveLetter "X:" -Server "my-server" -FullPath "\\my-server\x" -Credential $credential
If you do not want to use a hardcoded password in BuildCredential, but you want to prompt the user instead:
function GetCredential {
param(
$Label
)
$credential = Get-Credential -Message "Write your credentials for '$Label':"
if(!$credential) {
throw "A credential was needed to continue. Process aborted."
}
return $credential
}
Also, if instead of using $Server as a param, you want to extract it from $FullPath using regex, you can do that.
It presumes the $FullPath has the following format: \\server-name\dir1\dir2\etc
# Get server name using regex:
$FullPath -match '\\\\(.*?)\\.*?'
$Server = $Matches[1]
WMI commands can either receive explicit credentials as an argument (the -Credential flag), or run in the security context of the user running the script if no credentials are provided.
Right now, my script looks like this:
if ($Creds) { # if the user provided credentials
Invoke-WMIMethod -Credential $Creds <...something...>
... hundreds more lines of Invoke-WMIMethod code ...
else { # user did not supply credentials, use current security context
Invoke-WMIMethod <...something...>
... same exact hundreds of lines of Invoke-WMIMethod code, without -Credential ....
}
In other words, the only difference is the -Credential flag. Is there any way I can consolidate this huge if-else into one block of code?
Use splatting for dynamically passing arguments to a cmdlet, like this:
$params = #{
'Class' = 'Win32_Foo'
'ComputerName' = 'bar'
...
}
if ($cred) {
$params['Credential'] = $cred
}
Invoke-WmiMethod #params
or like this:
$optional_params = #{}
if ($cred) {
$optional_params['Credential'] = $cred
}
Invoke-WmiMethod -Class Win32_Foo -Computer bar ... #optional_params
The technique should already be available in PowerShell v2.0.
It doesn't look like the current security context is available to be passed in as a credential object (ref this question).
Fortunately, invoke-wmimethod's use of the credential attribute appears to behave as if it wasn't specified when provided a null value. So if $cred is empty then invoke-wmimethod -credential $cred <...something...> should behave the same as invoke-wmimethod <...something...>.
Now, even better might be just to keep the if else and remove any duplicate code. So, instead of:
if ($Creds) { # if the user provided credentials
Invoke-WMIMethod -Credential $Creds <...something...>
... hundreds more lines of code ...
else { # user did not supply credentials, use current security context
Invoke-WMIMethod <...something...>
... same exact hundreds of lines of code ....
}
You would have:
if ($Creds) { # if the user provided credentials
$myresults = Invoke-WMIMethod -Credential $Creds <...something...>
else { # user did not supply credentials, use current security context
$myresults = Invoke-WMIMethod <...something...>
}
... hundreds more lines of code using $myresults...
I have a powershell script. Executing this will create a session with remote computer and execute some scriptblock inside remote computer. After that execution I need to send a mail.
So, I get the arguments required (like from, to, subject, body, smtp server, credentials) etc locally as shown below:
$param = #{
SmtpServer = 'SMTPServer'
Port = 587
UseSsl = $true
Credential = $crede
From = 'server#domain.in'
To = 'userv#domain.in'
Subject = 'Hi'
Body = "Hello"
}
$crede has value (username explicitly given, password reading from a text file).
And I call that param as shown below:
Send-MailMessage $using:param
This is inside an Invoke-Command.
But when I run this program it asks me for the mail message details like from, to, smtp server etc.. Please note that these values are given on $param locally. I guess $param values are not being passed to the remote session.
Can someone please support me. Any help would be really appreciated.
I just had a similar issue.
$processName = myProcess.exe
$session = New-PSSession -ComputerName $anycomputer -Credential $credentials
# powershell syntax requires -Scriptblock and { on this line
Invoke-Command -Session $session -ScriptBlock {
param([string] $processName)
Get-Process -Name $processName
} -Args $processName
Remove-PSSession $session
$processName = myProcess.exe
$session = New-PSSession -ComputerName $anycomputer -Credential $credentials
Invoke-Command -Session $session {Get-Process -Name $using:processName}