I'm working on a script to copy a folder from a UNC path to a local server. I'm remotely running my script through an interactive session and utilizing Invoke-Command -ScriptBlock like so:
Invoke-Command -ComputerName MyServer -ScriptBlock $Script
This is the script to do the copying:
$script {
try {
New-PSDrive -Name MyDrive -PSProvider FileSystem -Root \\uncpathserver\e$\SourceCode\ -Credential Contoso\me
Copy-Item -Path \\uncpathserver\e$\SourceCode\* -Destination E:\Inetpub\Target -Recurse -Force
}
catch {
Write-Host "Failed to copy!"
}
}
It is failing and throwing my catch block every time. I can't seem to figure out what I am missing to get this to work - it seems so simple and I hope I'm not missing something blatantly obvious.
EDIT:
I was able to get it to work by now just running the script from my local PC instead of from a server. I'm calling the file copy out of $script block now as well. This is what the new code looks like:
$MyServers= #("server-01", "server-02")
foreach ($server in $MyServers)
{
$TargetSession = New-PSSession -ComputerName $server -Credential
contoso\me
Copy-Item -ToSession $TargetSession -Path C:\Source\TheCode\ -
Destination "E:\InetPub\wherethecodegoes" -Recurse -Force
}
Everything else I'm doing inside my $script block (which has been omitted here for troubleshooting sake) is working A-OK. I do have to enter my credentials for each server, but due to the small nature of servers I'm working with, that isn't a deal breaker.
Sounds like a 'kerberos double hop' problem.
Short-Answer
Avoid the problem. From your system, setup two PSdrives. Then copy \\uncpathserver\e$\SourceCode\ to \\RemoteIISserver\E$\Inetpub\Target\
Long-Answer
From your system (System A), you are remotely executing a script (on System B) that will copy a remote folder (from System C).
It should work, but it doesn't. This is because when you (specifically, your account) from System A, remotely connects to System B, then asks System C for something, 'System C' doesn't trust you.
A quick google of the problem will show a myriad of ways around this issue, however;
Not all methods are secure (example: CredSSP)
Not all methods will work on your version of Windows (which is...?)
Not all methods will work with PowerShell
One secure method that does work with PowerShell leverages delegation.
This can be a bit daunting to setup, and I suggest you read-up on this thoroughly.
## Module 'ActiveDirectory' from RSAT-AD-PowerShell Windows feature required.
$ServerA = $Dnv:COMPUTERNAME
$ServerB = Get-ADComputer -Identity ServerB
$ServerC = Get-ADComputer -Identity ServerC
Delegate 'Server B' to access 'Server C';
# Set the resource-based Kerberos constrained delegation
Set-ADComputer -Identity $ServerC -PrincipalsAllowedToDelegateToAccount $ServerB
# Confirm AllowedToActOnBehalfOfOtherIdentity.Access is correct (indirectly).
Get-ADComputer -Identity $ServerC -Properties PrincipalsAllowedToDelegateToAccount
Wait about 15 minutes for 'Server B' to sync-up (or just reboot it).
You can force this with the following (Note: $Cred should contain your credentials);
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
klist purge -li 0x3e7
}
Run a test-hop;
Invoke-Command -ComputerName $ServerB.Name -Credential $cred -ScriptBlock {
Test-Path \\$($using:ServerC.Name)\C$
Get-Process lsass -ComputerName $($using:ServerC.Name)
Get-EventLog -LogName System -Newest 3 -ComputerName $($using:ServerC.Name)
}
The downside is you have to setup every remote remote-target (every 'Server C') this way. But the upside is that it's secure.
Related
I start with a txt file named vms.txt.
It contains 2 servers like so:
server1
server2
When I run the script shown below, the command that is invoked to install VMware tools only runs on server2 and server1 gets skipped. Does anyone have any suggestions on how to modify this script to make it run on both servers in the txt file? I will have to run this in the future for hundreds of VMs, so I am trying to find an easy way to get this to loop properly.
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
foreach($session in $sessions)
{
Invoke-Command -Session $session -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
}
In your loop-based approach, the problem is your variable assignment:
# !! This only ever stores the *last* session created in $sessions,
# !! because the assignment is performed in *each iteration*.
foreach($vm in $vms){
$sessions = New-PSSession -ComputerName $vm -Credential $cred
}
The immediate fix is to move the assignment out of the loop:
# OK - captures *all* session objects created in $sessions
$sessions = foreach($vm in $vms){
New-PSSession -ComputerName $vm -Credential $cred
}
Taking a step back:
Both New-PSSession -ComputerName and Invoke-Command -Session accept an array of computer names / sessions, so there's no need for loops.
Passing multiple sessions / computer names to Invoke-Command has the big advantage that the operations run in parallel.
Note:
Invoke-Command has built-in throttling to avoid targeting too many machines at once. It defaults to 32, but can be modified with the -ThrottleLimit parameter.
Output from the targeted computers will arrive in no predictable order, but the output objects are decorated with (among others) a .PSComputerName property reflecting the originating computer - see the bottom section of this answer.
That is, your code can be simplified to:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
$sessions = New-PSSession -ComputerName $vms -Credential $cred
Invoke-Command -Session $sessions -ScriptBlock {
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI”
}
Important:
Sessions should eventually be cleaned up with Remove-PSSession when no longer needed.
However, that stops any commands running in those sessions, so if you've launched asynchronous operations via your Invoke-Command call, you need to ensure that those operations have finished first - see the comments re potentially asynchronous execution of your VMware-tools-10.3.5.exe application below.
Or, even simpler, if you only need to execute one command on each machine, in which case there is no need to create sessions explicitly, pass all computer names directly to Invoke-Command's -ComputerName parameter:
$cred = Get-Credential
$vms = Get-Content C:\Scripts\Tools\vms.txt
# Note the use of -ComputerName
Invoke-Command -ComputerName $vms -Credential $cred -ScriptBlock {
# Note the use of | Write-Output to ensure synchronous execution.
c:\users\jsmith\documents\VMware-tools-10.3.5.exe /v "/qn REBOOT=R Remove=AppDefense,VMCI” | Write-Output
}
Important:
If your application (VMware-tools-10.3.5.exe) runs asynchronously, you must ensure its
synchronous execution, otherwise it may not run to completion, because the implicitly created remote session is discarded when a script block returns from a given computer.
A simple trick to ensure synchronous execution of any external (GUI-subsystem) executable is to pipe it to Write-Output, as shown above (or Wait-Process, if it doesn't produce console output) - see this answer for an explanation.
I have done a ton of research on how to push this EXE to a remote PC using PSSession and all lines of code work when executed line by line. But i have had a hard time putting this into a function that makes sense and will execute all lines of code and successfully install the software with one push of a button. Not sure whats happening. It will install the exe locally when i tried to do put all lines of code in a function and run it. Can you please help instruct what i am doing wrong? Sorry i am a newbie at Powershell.
$dc1 = New-PSSession -ComputerName DC1
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $dc1
Enter-PSSession -Session $dc1
Invoke-Command -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-Pssession $dc1
Enter-PSSession is for interactive use only, so not suitable for use in a function.[1]
Instead of using Enter-PSSession, pass the session you've created with New-Session to the Invoke-Command command's -Session parameter, which will run the command in the context of that (remote) session.
# Define your function...
function Invoke-InstallerRemotely {
param([string] $ComputerName)
$session = New-PSSession -ComputerName $ComputerName
Copy-Item C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe -Destination C:\TPAdmin -ToSession $session
# Do NOT use Enter-PSSession.
# Pass your session to Invoke-Command with -Session
Invoke-Command -Session $session -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"
Remove-PSSession $session
}
# ... then invoke it.
# Note that functions must be defined *before* they're called.
Invoke-InstallerRemotely -ComputerName DC1
[1] Using it in a function means that an interactive session on the target computer is entered, which you must exit interactively (by typing and submitting exit or Exit-PSSession) before the remaining statements in the function are executed, again locally.
As for …
Sorry i am a newbie at Powershell.
… that's all fine, as we all had to start from somewhere. However... a couple of things here:
Please be sure to format your posts, to encourage folks to want to
help. People frown on no doing that. Having to copy, paste and
reformat your post is well, extra unnecessary work. ;-}. We've al been there.
We have no idea how you are getting up to speed on PowerShell, but
use the freely available resources to limit/avoid all the
misconceptions, frustrations, errors, potential bad habits, etc.,
that you are going to encounter. Live on watching the videos on:
YouTube
Microsoft Virtual Academy
MSDN Channel9
Microsoft Learn
as well as the reference
and eBook resources.
Back to your use case. You do not say what is happening. So, you leave us to guess. Which is not really potentially helpful to you.
Nonetheless, you should just need to do this... in PowerShell v5x as it's require to use the -ToSession argument.
$DC1 = New-PSSession -ComputerName 'DC1'
Copy-Item -ToSession $DC1 -Path 'C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe' -Destination 'C:\TPAdmin'
Invoke-Command -Session $DC1 -ScriptBlock {C:\TPAdmin\Greenshot-INSTALLER-1.2.10.6-RELEASE.exe /VERYSILENT /LOG="C:\SOFTWAREINSTALL.LOG"}
Remove-PSSession -Session $DC1
I am not sure why you are doing that Enter-PSSsssion in the New-PSSession command as it is not needed. It's for standalone interactive sessions.
Explicit PSRemoting = Enter=PSSEssion
Implicit PSREmoting = New-PSSEssion
If all else fails for you on the copy via the session, then just use the normal UNC way to copy from source to destination.
Copy-Item -Path 'C:\temp\Results.csv' -Destination "\\$($DC1.Computername)\c$\temp"
See also:
Copy To or From a PowerShell Session
I'm trying to create a script that connects to various servers, should attach a PSDrive and copy over files. The problem resides in that I an unable to pass the variable into the Invoke-Command script-block.
workflow kopijobb {
param ([string[]]$serverList, $creds, $basePath)
foreach -parallel ($server in $serverList){
# Use the sequence keyword, to ensure everything inside of it runs in order on each computer.
sequence {
#Use the inlinescript keyword to allow PowerShell workflow to run regular PowerShell cmdlets
inlineScript{
$path = $using:basePath
Write-Host "Starting $using:server using $path"
#Create session for New-PSSession
$session = New-PSSession -ComputerName $using:server -Credential $using:creds
# Copy Java and recreate symlink
Invoke-Command -Session $session -ScriptBlock {
# Make a PSDrive, since directly copying from UNC-path doesn't work due to credential-issues
New-PSDrive -Name N -PSProvider FileSystem -root $using:path -Credential $using:creds | out-null
I pass the network path to $basePath, and I'm able to read it inside the inlineScript block (where I have tried storing it in a new variable to test), but once I try accessing it in the New-PSDrive command, the variable is suddenly empty/unreachable, and the mounting of the drive fails with the error Cannot bind argument to parameter 'Root' because it is null.
I'm at a loss to why this fails, so I'm turning to the collective wisdom here instead.
If feels embarrassing to answer my own question, especially on the same day, but I bumped into a PowerShell guru at work and he took one glance at the script and saw the problem:
I had to add -Args to the Invoke-Command
Invoke-Command -Session $session -ScriptBlock {
param($srv,$login,$path,$...)
#Make a PSDrive, since directly copying from UNC-path doesn't work due to credential-issues
New-PSDrive -Name N -PSProvider FileSystem -root $path -Credential $login | out-null
} -Args $using:server,$using:creds,$using:basePath,$using:...
This does of course mean that I had to import all the needed arguments from the top level into the workflow, and then into the Invoke-Command.
When running the below code, i can put anything in the block at the bottom - I'm trying to copy a folder across to run an exe from a local folder and perform an install of that exe during the remote session to remote machines. I am getting Access Denied Errors. I read, i cant use the Kerberos Delegation Cmdlets which are only for a forest level of 2012 and above. Current Site has Domain Functional Level 2008 R2. Is there another way to achieve copying the files across during each remote session to the computers specified in the text file?
Thanks in advance
########################################
$Cred = Get-Credential DOMAIN\USER
$Computers = Get-Content C:\tab.txt | Where-Object { $_ }
ForEach ($Computer in $Computers)
# {
# if (Test-Connection -ComputerName $Computer -BufferSize 16 -Count 1 `
-Quiet)
{
# Creates a new remote PowerShell Session and script block - enter
the code you want to execute remotely from this block
$Session = New-PSSession $computer -Credential $cred
Invoke-Command -Session $Session -ScriptBlock {
Copy-Item -Path "\\print-server\pcclient\win\*" -Destination
"c:\pcclient" -Force -Recurse -Verbose
# Start-Sleep -s 10
# Start-Process "\\Print-Server\PCClient\win\client-local-install.exe" -ArgumentList "/SILENT"
}
}
Remove-PSSession -Session $Session
# }
This is because you're on a remote machine, trying to access another network resource. When you connect to the remote machine in PowerShell, you're effectively connected/authenticated to that machine only, (unless you specify otherwise) it doesn't have access to your credentials to access the network share, so the connection to the network share is treated as unauthenticated, hence the failure.
This article https://blogs.technet.microsoft.com/heyscriptingguy/2012/11/14/enable-powershell-second-hop-functionality-with-credssp/ covers it well, essentially in you will need to run this locally (to allow your machine to pass credentials):
Enable-WSManCredSSP -Role Client -DelegateComputer * -Force
On the server run (to allow the server to accept these credentials):
Enable-WSManCredSSP -Role Server –Force
And update your New-PSSession command to:
$Session = New-PSSession $computer -Credential $cred -Authentication CredSSP
If you want, you can share your credentials with only specific machines, or subsets of a domain using *.yourdomain.lan or whatever, if you connect to multiple machines, then it's easier to use -DelegateComputer *.
I already have code written to connect to another computer in my house and pull music files from the C drive. However, I am trying to find out how to keep this code, but modify it in a way that I can use it to run code on the second computer, then save it to a text file.
foreach ($server in Get-Content .\serverList.txt){
psexec \\$server -u username-p password cmd /c dir c:\*.mp3 /s > c:\Powershell\$server.txt
}
You could write a book on PowerShell Remoting (several people have) but it's reasonably straightforward.
On both computers run Enable-PSRemoting to configure all the settings. Then on the originating computer (the one making the remote call) run Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' (if you are security conscious replace the * with the IP of the remote PC).
Then you can run the all-powerful Invoke-Command to do all sorts of awesome stuff remotely. Unless you're on a domain or there's an identical user on the remote PC you'll need to provide credentials which means either prompting for them or saving them, but if I go into too much detail we'll both be here all day. Pretty easy to find the answers on Google.
$cred = Get-Credential
foreach ($server in Get-Content .\serverList.txt) {
Invoke-Command $server -Credential $cred -ScriptBlock { Get-ChildItem C:\*.mp3 -Recurse } | Out-File C:\Powershell\$server.txt
}