PowerShell - Cannot validate argument on parameter 'Identity'. The argument is null - powershell

Hi I am new developing scripts in powershell and Active Directory, I am trying to run the following .ps1
#! /usr/bin/pwsh
param ([String]$dns, [String]$adminUser, [String]$adminPassword, [String]$user, [String]$newPassword)
$password = ConvertTo-SecureString -String $adminPassword -AsPlainText -Force
$pass = ConvertTo-SecureString -String $newPassword -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($adminUser, $password)
$session = New-PSSession -cn $dns -Credential $credential -Authentication Negotiate
Invoke-Command -Session $session -ScriptBlock {Set-ADAccountPassword -Identity $user -Reset -NewPassword ($pass)}
passing the arguments like this
./changePasswordAD.ps1 mydns myuser mypassword userToEdit NewPassword
the result obtained is the following
Set-ADAccountPassword: Cannot validate argument on parameter 'Identity'. The argument is null. Provide a valid value for the argument, and then try running the command again.
Why does this happen and how can I solve it?
I am grateful in advance for your contributions

It's a scope problem. A ScriptBlock can't directly reference variables defined in another PowerShell session. New sessions are used when remoting or relying on jobs. This is hinted at by the presence and use of New-PSSession, Enter-PSSession, etc. when PSRemoting, but isn't as obvious when using jobs.
You can prefix the variable reference in the ScriptBlock with $using: like so in order to "use" the variable defined within the calling session:
{
Set-ADAccountPassword -Identity $using:user -Reset -NewPassword $using:pass
}
However, the using scope can't be used when running a ScriptBlock on the local system. Referencing a local variable would work fine if you executed the ScriptBlock locally within the same session (using the call operator &, Invoke-Command is not usually recommended for local use since it unnecessarily relies on PSRemoting). So what if we want it to run anywhere?
If the ScriptBlock needs to be able to function regardless of execution context, you can use the-ArgumentList parameter of Invoke-Command (this also applies to cmdlets like Start-Job when execution would be local but within a new session) instead to pass the variables into the ScriptBlock:
Invoke-Command -ArgumentList $user, $pass {
Param( $user, $pass )
Set-ADAccountPassword -Identity $user -Reset -NewPassword $pass
}
I added line breaks for readability but both ScriptBlocks above should function as a one-liner as well.
Note: Technically, parameters passed via -ArgumentList would be referenced as $args[0], $args[1], etc. in the order provided such as when processing raw script arguments, but as executing a ScriptBlock functions similarly to a script or... well, function, adding the Param( .... ) bit will cause the arguments provided to be assigned to positional parameters defined in the ScriptBlock; in this case, the friendlier names $user and $pass. The param names do not need to be the same as the original variable names in the parent scope.

Related

Powershell: Cannot read and write a variable on Hyperv-V VM from host

Using this Powershell script I try to write and read a variable on VM from host.
$Username = 'administrator'
$Password = 'password'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
#Added value to a variable on VM
Invoke-Command -VMName VM_Windows_2016 -Credential $Cred -ScriptBlock {$InstallPath="C:\Install\install-1.ps1"}
#Trying to read the variable on VM but with no result
Invoke-Command -VMName VM_Windows_2016 -Credential $Cred -ScriptBlock {Write-Host($InstallPath)}
As you see the result is empty. Can anyone help me to show how to write and read an variable on VM from host machine? Thanks!
When using Invoke-Command to run a command remotely, any variables in the command are evaluated on the remote computer. So when you run the first Invoke-Command you are only defining the variable $InstallPath and terminating the remote PS session. When you are run the Invoke-Command second time it create entirely new PS session, hence InstallPath would be null. Instead of this you can define and read the variable in a single Cmdlet like this.
$remoteScriptblock = {
$InstallPath = "C:\Install\install-1.ps1"
Write-Host($InstallPath)
}
Invoke-Command -VMName VM_Windows_2016 -Credential $Cred -ScriptBlock $remoteScriptblock
If you still want to run this in multiple Cmdlets you may consider Run a command in a persistent connection

Passing credentials from one powershell script to another

Im trying to pass a credential to another powershell script but i get an error as
"Cannot convert the "System.Management.Automation.PSCredential" value
of type "System.String" to type
"System.Management.Automation.PSCredential""
This is the script which invoke the psscript
param(
$vcenterserver,
[System.Management.Automation.Credential()]$vccredential
)
#New-Item C:\dcpromotxt\1.ps1 -ItemType file -Force
#Start-Process powershell.exe -ArgumentList "-NoExit -File '& 'C:\dcpromotxt\1.ps1''" -vcenterserver $vcenterserver -vccredential $vccredential
Start-Process powershell -ArgumentList "-NoExit -File '& 'C:\dcpromotxt\1.ps1''","$vcenterserver","$vccredential"
and here is the 1.ps1
param(
$vcenterserver,
$vccredential
)
Connect-VIServer $vcenterserver -Credential $vccredential
start-sleep 120
You cannot pass a Powershell object via comand line, these will be converted to strings and become unusable. Worse, "$vccredential" returns the type name due to toString() implementation. You can pass a PSCredential object to your script if you invoke it in your current session, like this:
& 'C:\dcpromotxt\1.ps1' $vcenterserver $vccredential
This way your parameters won't be converted and will retain internal structure.
If, however, you require a separate Powershell process to work with the new script, you can convert a PSCredential into two strings, namely $cred.username and (ConvertFrom-SecureString $cred.password), which you can reassemble on the destination side via $cred=new-object PSCredential($username,(convertto-securestring $password)). The restriction with this process is that your other Powershell process should run under the same user account and on the same computer. But you can optionally supply the conversion cmdlets with -key parameter that contains 128, 192 or 256 bits (384 probably on Win8+) which will be used in AES encryption algorithm, this will allow you to run that Powershell process as another user or on another PC and use shared key to encrypt/decrypt sensitive data. As a matter of extra precaution, you can use this module to add additional "salt" (named "entropy" in that article) to your encryption, so that even intercepting the secure string and the key won't make an attacker to decrypt your data without known entropy.
You can try this method then, save the cred to disk with different key, then modify the ps1 file to load the cred from disk, like this:
First: Save the Cred to disk
$credential = Get-Credential
$Key = [byte]1..16
$credential.Password | ConvertFrom-SecureString -Key $Key | Set-Content c:\cred.key
then edit the ps1 file like this for example:
param(
$vcenterserver
)
Add-PSSnapin VMware.VimAutomation.Core
$Key = [byte]1..16
$username = "type the username"
$encrypted = Get-Content c:\cred.key | ConvertTo-SecureString -Key $Key
$credential = New-Object System.Management.Automation.PsCredential($username, $encrypted)
Connect-VIServer $vcenterserver -Credential $credential
then run it:
Start-Process powershell -ArgumentList "-noExit -File c:\vcenter.ps1 -vcenterserver vcenter"
You can't pass a credential object in an argument string. Call your second script like this:
& 'C:\dcpromotxt\1.ps1' $vcenterserver $vccredential
A requirement to run the second script via Start-Process doesn't make sense.

Trouble calling powershell script from within powershell with arguments

I have spent the last 4 hours on this issue and would greatly appreciate any input you might have.
I need to call a powershell script with different credentials and pass arguments onto that script.
Following the installation of a program wrapped in WISEScript this script kicks off to gather AD accounts for the machine and remove them from specific AD Security Groups. Unfortunately as the script runs locally I cannot use ActiveDirectory modules in powershell as not all machines in our environment have RSAT.
The initial script is run from an elevated account on the machine:
$creds = New-Object System.Management.Automation.PsCredential("DOMAIN\USER", (ConvertTo-SecureString "Password" -AsPlainText -Force))
$ProfileGUIDS = Get-ChildItem 'hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileGuid'
$Groups = [ADSI]"LDAP://CN=Group4d_test,OU=GroupMigrationTesting,OU=TestOU,OU=US,DC=DOMAIN",[ADSI]"LDAP://CN=Group3d_test,OU=GroupMigrationTesting,OU=TestOU,OU=US,DC=DOMAIN"
Function Get-DistinguishedName ($strUserName)
{
$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$searcher.Filter = "(&(objectClass=User)(samAccountName=$strUserName))"
$result = $searcher.FindOne()
if ($result)
{
Return $result.GetDirectoryEntry().DistinguishedName
}
}
forEach ($GUIDkey in $ProfileGUIDS)
{
$GUID = Out-String -InputObject $GUIDKey
$index = $GUID.IndexOf("S-1")
$GUID = $GUID.Substring($index)
$GUID = $GUID.Substring(0,128)
$index = $GUID.IndexOf(" ")
$GUID = $GUID.Substring(0,$index)
$Profile = "hklm:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$GUID"
$ProfileItems = Get-ItemProperty $Profile
$SAM = $ProfileItems.ProfileImagePath
$index = $SAM.LastIndexOf("\")
$index ++
$SAM = $SAM.substring($index)
$UserDN = Get-DistinguishedName $SAM
$User = [ADSI]"LDAP://$UserDN"
if($User -ne $null)
{
forEach($group in $groups)
{
Right here is where I need to call the 2nd script with different credentials.
This is RemoveUsers.ps1, the script I need to run with different credentials:
param
(
[string]$group = "MyDefaultSAM",
[string]$user = "MyDefaultUser"
)
$Group.remove($User.ADsPath)
I have tried:
start-process powershell.exe -Credential $creds -NoNewWindow -ArgumentList "Start-Process $PSSCriptRoot\RemoveUsers.ps1 -Verb
This will run the script however I cannot specify any arguments
powershell.exe -file "$PSScriptRoot\RemoveUsers.ps1" -user $user -group $group
This calls the script with arguments but does not allow for the -Credentials switch
I have also tried:
$job = Start-Job -ScriptBlock {
powershell.exe -file "$PSScriptRoot\RemoveUsers.ps1" -user $user -group $group
} -Credential $creds
This runs but does not appear to work properly as the users remain in the AD groups.
Any help is appreciated.
Thanks - Jeff
**** UPDATE ****
Thanks for the information. When I add the changes you suggest I receive an error
Invoke-Command : Parameter set cannot be resolved using the specified named parameters
It appears, as I have found online, the -Credential switch cannot be used without the -Computer switch. If I specify $env:COMPUTERNAME or localhost for the computer I receive the error
\RemoveUsers.ps1 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
I can avoid this issue if I remove the -Credential switch and open the AD group to everyone. At this point I don't need to elevate a new powershell script and can add the command in the same. If I cannot resolve the issue with Invoke-Command this is likely what I will do.
**** UPDATE ****
What I ultimately had to do was use -Authentication Credssp in the argument list as there is an issue with using the AD Module via Invoke-Command. In addition I had to start the Win-RM service, Enable WSMacCredSSP (-role client on each machine and add a DelegateComputer entry and -role server on the server connecting to). Only after the service was started and an entry was made for WSManCredSSP was I able to use the Invoke-Command switch and have the AD Module work correctly.
This of course makes things more complicated and I decided just installing the AD Module on each PC (after finding a way to do it without RSAT) and forgetting about running the command remotely all together. Thanks for your help with the matter.
Thanks
You don't need to run PowerShell scripts with powershell.exe when calling them from another PowerShell script. Simply use the call operator (&). Also, I'd use Invoke-Command for running something inline with different credentials.
Beware that the scriptblock doesn't automatically know about the variables in the rest of your script. You need to pass them into the scriptblock via the -ArgumentList parameter. That is most likely the reason why removal didn't work when you ran RemoveUsers.ps1 as a job.
Something like this should work:
Invoke-Command -ScriptBlock {
& "$PSScriptRoot\RemoveUsers.ps1" -user $args[0] -group $args[1]
} -ArgumentList $user, $group -Credential $creds -Computer $env:COMPUTERNAME
This requires PSRemoting, though (run Enable-PSRemoting as an administrator).

Call another powershell pass parameter use different creds

I want to call another secondary powershell script from within my primary powershell script. I want to pass it a parameter from the primary script, the secondary script needs the username parameter, I want to pass to it, from the primary, and then have the secondary script Im calling use different credentials. I think I might be able to use invoke-command, I just dont know all the syntax, anyone able to post some examples of what I want to accomplish, and then I'll fill in the blanks if need be?
Thanks in advance! :-)
Assume that your secondary script looks like this:
param (
[string] $Username = $args[0]
)
Write-Output -InputObject $Username;
You can use the Start-Process cmdlet to launch the script with alternate credentials.
$Credential = Get-Credential;
Start-Process -Wait -NoNewWindow -FilePath powershell.exe -ArgumentList '"c:\path\to my\file.ps1" -Username "UsernameGoesHere!"' -Credential $Credential;
Or you can use the Invoke-Command cmdlet:
Invoke-Command -FilePath 'c:\path\to my\script.ps1' -Credential $Credential -ArgumentList "UsernameGoesHere!";
I got it, thanks to Trevor Sullivan for pointing me in the right direction.
I ended up just putting my second ps1 file into a scriptblock, and running it as a job, and passing it the arguments from the main script, like this
$job = Start-Job -scriptblock {
param ($username)
some code to run against the variable that was passed in
} -Args $target -credential $Cred
$target being the variable I want to pass to my scriptblock
$username being the parameter that the scriptblock accepts
Thanks.

Run ScriptBlock with different credentials

I have a script, that determines a userid; once I have that userid, I want to run a script block against that userid using different credentials. Is this possible? Can anyone show me examples of this?
I got it, thanks to Trevor Sullivan for pointing me in the right direction. I ended up just putting my second ps1 file into a scriptblock, and running it as a job, and passing it the arguments from the main script, like this
$job = Start-Job -scriptblock {
param ($username)
some code to run against the variable that was passed in
} -Args $target -credential $Cred
$target being the variable I want to pass to my scriptblock.
$username being the parameter that the scriptblock accepts Thanks.
I know this was answered a long time ago, but I thought I'd add another option for those looking that returns data without having to retrieve it.
We can create a helper script that creates a pscredential and then uses it to start a local PSSession to run a script or scriptblock in a different user's context. You need to get the user password from somewhere, preferably entered as a secure string or retrieved from a Key Vault, but for the example our helper script will take it as a string parameter.
Script contents:
param ([string]$username,[string]$password)
$Username = 'username#domain.com'
$Password = ConvertTo-SecureString -String $password -AsPlainText -Force
$Credential = New-Object -Type PSCredential($Username,$Password)
$Session = New-PSSession -Credential $Credential
Invoke-Command -Session $Session -FilePath C:\Path\to\some\script.ps1
You can also use -ScriptBlock instead of -FilePath if you have a simple chunk of code to run or you have converted a script to a script block.
Hope this helps somebody out!
Security context for a session is established when the session is initialized. You can't arbitrarily run commands under a different context within the session. To run under a different security context (set of credentials) you'll need to initialize a new session under those credentials and run it there.
If you look at the help for Invoke-Command, you'll note that the -Credential parameter is only valid in parameter sets that specify a remote session by computername, uri, or session. You can also use -credential with Start-Job, which will run the command in a new session on the local machine.
This code will launch PowerShell in Administrator mode using the credentials provided and then run the code in the script block. There might be others ways but this works for me.
$account= # AD account
$password = # AD user password
$passwordSecure = ConvertTo-SecureString ($password) -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential ($account, $passwordSecure)
$ScriptBlock = {
whoami
start-sleep 3
}
 
# Run PowerShell as Administrator with Custom Crednetails
start-Process powershell.exe -Credential $Cred -ArgumentList "-Command Start-Process powershell.exe -Verb Runas -ArgumentList '-Command $ScriptBlock'" -Wait