Trouble calling powershell script from within powershell with arguments - powershell

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).

Related

Wait for remote Powershell script to finish

I'm creating a powershell script to import Dynamics NAV Application Objects to my Dynamics NAV 2018 database.
Microsoft is providing a PS cmdlet - Import-NAVApplicationObjects - to do so, but I have trouble to wait for the command to finish.
I have a calling script which does the following
$PSSession = New-PSSession -ComputerName $TargetMachine -Credential $MyCredential
Invoke-Command -Session $PSSession -ArgumentList $Database_Name_user, $MyCredential -ScriptBlock {
$process = Start-Process powershell.exe -Verb RunAs -Wait -PassThru -ArgumentList "-File `"C:\Users\User\Desktop\Database\Import1.ps1`" $args[0]"
$process.WaitForExit()
if ($process.ExitCode -ne 0) {
throw $process.StandardError.ReadToEnd()
}
}
The script Import1.ps1 on my $TargetMachine looks like this
param(
[String]$Database_Name_user
)
try {
$AllFiles = "C:\Users\User\Documents\AllFiles.txt"
$Modules = "C:\GIT\Loading Components\NAVModuleImport.ps1"
$OutputBuffer = import-module $Modules
Import-NAVApplicationObject -Path $AllFiles -DatabaseServer "SQLSRV001" -DatabaseName $Database_Name_user -SynchronizeSchemaChanges "No" -ImportAction "Overwrite" -Confirm:$false | Out-Null
}
catch{
$_ | Out-File "C:\Users\User\Documents\Import1.txt"
}
The file AllFiles.txt has a size of 220 MB and contains more than 7700 Dynamics NAV Application Objects (tables, pages, codeunits and so on).
When I launch the script which executes Import-NAVApplicationObject directly from the remote computer stored in $TargetMachine everything works smootly and the process takes up to 10 to 15 minutes, as expected.
When calling the script as shown in the first code example the output stops for a minute and then says everything is done.
Any help is appreciated, thank you in advance.
Edit: Solution
I noticed that my scripts as shown are working, the Import-NAVApplicationObjects cmdlet just failed.
When I elevate the powershell process on the remote computer and run the import, the cmdlet tried to authenticate as NT-Authority\Anonymous to the database.
Then I passed the credentials of the user that opens the remote PSSession to Import1.ps1 and used the parameters -UserName and -Password of the import cmdlet.
Sadly this process failed again.
Now I tried some things with the user I want to use for authenticating, changing passwords etc and it worked! The password contained a semicolon ; and apparently the import cmdlet was not happy with that.
So my simple solution is: Removing the semicolon from the password.

Run Get-ClusterGroup on remote server using Central server

I have a centralized server from which i can run the following PowerShell command to get the clustergroup of cluster servers.
Enter-pssession -computername (ip-address) -credential (domain user)
And it prompts me to enter password then i get the session and execute
get-clustergroup
Okay till this it is fine.
Now i wanted to make this fully automated by converting in to a PowerShell script
The following commands works well when i run it in Powershell ISE and gets me the output of get-clustergroup
$password = ConvertTo-SecureString "password" -AsPlainText -Force
$user = "domain\user"
$cred = New-Object System.Management.Automation.PSCredential ($user,$password)
Enter-PSSession -ComputerName IP.Add.RE.SS -Credential $cred
get-clustergroup
but when i save the about script and run with PowerShell i get the following error.
get-clustergroup: the cluster service is not running
I want to automate the process by writing script to get get-clustergroup output of four cluster servers.
i am new to PowerShell scripting. how can i save the output?
Instead of creating a session to the other server, you can run the following which will run the command on the remote computer and return the output to your console:
Invoke-Command -ComputerName <IPAddress> -ScriptBlock { Get-ClusterGroup } -Credential $cred
You can store that output into a variable if you wish for future retrieval.
Since -ComputerName can accept an array object, you can modify your command to include all four of your servers. Below shows how to use all of your computer names and store the output in the variable $Output:
$Output = Invoke-Command -ComputerName "Server1","Server2","Server3","Server4" `
-ScriptBlock {Get-ClusterGroup} -Credential $cred
$Output
Your computer names could also be stored in a variable as an array. Then that variable can be used in your -ComputerName parameter:
$Computers = "Server1","Server2","Server3","Server4"
Invoke-Command -ComputerName $Computers -ScriptBlock { Get-ClusterGroup } -Credential $cred
See Invoke-Command for more information.

The Using variable is not supported in the script function or filter

I am running into a little issue, I have generated runbooks using powershell.
One of them connects to Vmware using a build up similar to this
The code
TempFunction{
connects to vmware using VMuser1 credentials
runs code etc...
All is well
return $TempVariable
}
$TempVariable = Invoke-Command -ScriptBlock ${function:TempFunction} -ComputerName localhost
This all works fine.
The problem is that when I introduce a global array named InfoArray that stores information along the way and at the end wish to write it to a different server using other credentials it fails stating "Invoke-Command: The Using variable is not supported in the script function or filter"
Here is my introduced code:
#Append gathered log information to log
$strScriptUser = "domain\FileUser"
$strPass = "Password01"
$PSS = ConvertTo-SecureString $strPass -AsPlainText -Force
$cred = new-object system.management.automation.PSCredential $strScriptUser, $PSS
Start-Job -ScriptBlock { Add-Content $using:Logfile $using:InfoArray } -Credential $cred
return $TempVariable
}
Any ideas as to how I could solve this issue?
The following does not work
Start-Job -ScriptBlock { Add-Content $Logfile $InfoArray } -Credential $cred
Regards
Akr
Pass them as arguments to this script block, since it doesn't support using.
Sample
Start-Job -ScriptBlock {
param($logfile,$infoArray)
Add-Content $Logfile $InfoArray
} -Credential $cred -ArgumentList #($logfile,$infoArray)
I attempted to Invoke without using -computername localhost, which resulted in it working via a PS script.
The challenge was that I cannot load PowerCLi pssnapin as a 64 bit directly in system center orchestrator, and must invoke for it to work.
Finally, what seemed to work was running the function using the server name rather than localhost.
$TempVariable = Invoke-Command -ScriptBlock ${function:TempFunction} -computerName *ServerName*

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

Powershell remote application(.cmd) deployment

I am beginner with PowerShell and struggling to get this around with the help from different sites, My requirement and scenario is
I have a windows server 2008(rktdepy) with PowerShell installed and I have packaged application with a .cmd file. When I click this .cmd file the application will be deployed.
The server name is rktdepy and I want to create a PowerShell script which will connect to other servers in the network (the server names should be picked up from a txt files) and install the application accessing the file remotely from rktdepy server. The files are not supposed to be copied to any server and should not use psxec for security reason.
So far I have used invoke and mapping the network drive but still I have issues
$Comsession = Get-content c:\adminfiles\scripts\deploy.txt | new-pssession -throttlelimit 50
Invoke-command -computername RKTDEPLY54 -scriptblock { (new-object -comobject wscript.network).mapnetworkdrive("R:", "\\rktdepy\deploy", $true) }
Invoke-command -session $comsession -scriptblock {"CMD /C r:\QR_DEPLOY.CMD"}
The above script throws error,
I dont want to use any password in the script and it should fetch the current logged in user password from rktdepy server. I is ok if the scripts prompts for a user name and password which will have admin access to all servers.
It looks like you are dealing with a couple problems. One is that the session where you map the drive is gone when you run the next Invoke-Command that uses the mapped drive. You could move that into the same script block to fix a problem like that. The second one is a "second hop" issue. See a resource like Don Jones' Secrets of PowerShell Remoting free ebook on http://powershell.org/wp/books.
Steve
I have testing the following on my machine and it is working so far. There is also another method you can try out listed below.
Method1:
1. I have txt file with a list of computers named allcomputers.txt. It contains name of machines on each line.
Machine10
Machine20
Machine30
Machine40
The deployment script (mydeploytest.ps1) which accepts Computername, Username and Password as input and creates a new PSSession and then invokes command.
param(
[string]$ComputerName,
[string]$User,
[string]$pass
)
Get-PSSEssion | Remove-PSSession
$session = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $session -ScriptBlock {
param(
[string]$ComputerName,
[string]$Username,
[string]$Password
)
$net = new-object -ComObject WScript.Network
$net.MapNetworkDrive("U:", "\\RKTDEPY\deploy", $false, $Username, $Password)
Invoke-Expression "CMD /C U:\deploy.cmd"
$net.RemoveNetworkDrive("U:")
} -args $ComputerName,$User,$pass
Get-PSSEssion | Remove-PSSession
Powershell commandline oneline to accomplish deployment task.
PS C:> Get-Content C:\scripts\allcomputers.txt | Foreach { C:\scripts\mydeploytest.ps1 $_ "yourserviceaccount" "password"}
Method2:
The help method for Invoke-Command has an example on how to solve the doublehop issue stevals is mentioning in the answer.
PS C:\> Enable-WSManCredSSP -Delegate Server02
PS C:\>Connect-WSMan Server02
PS C:\>Set-Item WSMan:\Server02*\Service\Auth\CredSSP -Value $true
PS C:\>$s = New-PSSession Server02
PS C:\>Invoke-Command -Session $s -ScriptBlock {Get-Item \\Net03\Scripts\LogFiles.ps1} -Authentication CredSSP
-Credential Domain01\Admin01
I think with little modification to method 2 you can achieve what you want.