Copy/transfer file using PowerShell 2.0 - powershell

I'm trying to create a PowerShell script to copy a file from my local computer to remote computer using its IP address.
I tested my client-server connection by using this command:
Invoke Command -ComputerName <IP Address> -ScriptBlock {ipconfig} -Credential $credential
(where $credential had been entered just before this command).
I tried using the Copy-Item and Robocopy commands but I'm not clear if it will even take my credentials and then let me copy a file from a local to a remote machine. To be specific, would these even support local to remote file transfer?
A lot of times I faced errors like:
Bad username and password, source path does not exist or destination path does not exist. But I still wanted to be sure if I was on right track and using the right commands to implement what I want to or if there is something else which I should consider using. How can I fix this problem?

It looks like you're trying to copy a file using PowerShell remoting. As posted in other answers, it would be simpler to use Copy-Item and/or Robocopy to copy from the source to a share on the destination computer.
If you want to copy the file using PowerShell remoting, you can slurp the file into a variable and use it in the remote script block. Something like:
$contents = [IO.File]::ReadAllBytes( $localPath )
Invoke-Command -ComputerName <IP Address> `
-Credential $credential `
-ScriptBlock { [IO.File]::WriteAllBytes( 'C:\remotepath', $using:contents ) }
Of course, if the file you're reading is really big, this could cause the remote connection to run out of memory (by default, PowerShell limits remote connections to around 128MB).
If you're stuck using PowerShell 2, you'll need to pass the bytes as a script block parameter:
$contents = [IO.File]::ReadAllBytes( $localPath )
Invoke-Command -ComputerName <IP Address> `
-Credential $credential `
-ScriptBlock {
param(
[byte[]]
$Contents
)
[IO.File]::WriteAllBytes( 'C:\remotepath', $Contents)
} `
-ArgumentList #( ,$contents )
And yes, you must wrap $contents in an array when passing it as the value to the -ArgumentList parameter and it must be prefixed with the comma operator ,, otherwise your remote script block will only receive the first byte.

One of the nice things about PowerShell is (unlike DOS) it support UNC paths. So you can literary just do:
Copy-Item -Path <local file path> -Destination \\<server IP>\<share>\<path>
Of course your account will need to have access to that location. If you need to enter alternate credentials you can pre-authenticate using net use \\<server IP>\<share> /user:[<domain>\]<user> <password>

Related

Start-process: both positional parameter and can't locate file specified

Ok, So I am working on the powershell remote deployment of software to ADUsers.
I have remote access to the computer and all, no problems there.
Right now I am at the point where I have a exe file from Ninite to just install 7zip onto the client pc just to see when it works so i can start deploying some bigger programs to it.
The guide I have found to help me deploy out the software for now looks like this:
Invoke-Command -ComputerName *REDACTED* -Scriptblock {
Start-Process C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe '/silent' -wait
}
When I do run this code, I get the error:
A positional parameter cannot be found that accepts argument 'Installer.exe'.
So I thought to myself, that it might be because of the spaces in the name. So therefore I changed it to:
Invoke-Command -ComputerName *REDACTED* -Scriptblock {
Start-Process C:\Users\Administrator\Documents\ninite_7_zip\Ninite_7Zip_Installer.exe '/silent' -wait
}
And ofcourse also changed it's name within the folder to match the "newly made" code.
But the error now changed into:
This command cannot be run due to the error: The system cannot find the file specified
Even though I use Powershell ISE, and I used it's guideboxes when writing, to enter the folder and find it, when I wrote the directory.
My only goal in this, is that I want to remotely run and complete this installer on the client PC when deployed from the DC upon which the file lies.
Anybody got a qualified guess? Or maybe even so, a solution.
Thanks in advance for your kind answers.
When I do run this code, I get the error:
A positional parameter cannot be found that accepts argument 'Installer.exe'.
You'll want to use quotation marks to qualify path names with spaces in them:
Start-Process 'C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe' '/silent' -wait
But the error now changed into:
This command cannot be run due to the error: The system cannot find the file specified
Even though I use Powershell ISE, and I used it's guideboxes when writing, to enter the folder and find it, when I wrote the directory.
ISE is not smart enough to realize that the scriptblock is to be executed on a remote computer, so it completes the path based on your local file system.
You still need to copy the executable to the remote machine in order to execute it:
# first copy the installer to remote file system
$remoteSession = New-PSSession -ComputerName $computerName
$localInstaller = 'C:\Users\Administrator\Documents\ninite_7_zip\Ninite 7Zip Installer.exe'
$remotePath = Invoke-Command -Session $remoteSession -ScriptBlock { $env:TEMP }
Copy-Item $localInstaller -Destination (Join-Path $remotePath "7zInstaller.exe") -ToSession $remoteSession
# now we can invoke the executable on the remote machine (re-using the same remoting session)
Invoke-Command -Session $remoteSession -ScriptBlock {
Start-Process (Join-Path $env:TEMP "7zInstaller.exe") '/silent' -Wait
}
# clean up
$remoteSession |Remove-PSSession |Out-Null

Can I RDP through mstsc and run a script on that remote desktop?

I'm writing a script which needs to RDP to a few servers, do processes there and then come back.
mstsc /v:<computer> by itself looks great as it's security/credential prompt is the same as if you manually executed it.
However, after some research it appears that's meant to be a command line utility and nothing more because trying things like:
mstsc /v:104.209.198.181 | Invoke-Command -ScriptBlock {"New-Item C:\Users\<me>\Desktop\Success.txt -ItemType file"}
doesn't work.
So I tried Enter-PSSession <computer> -Credential $env:UserName which people use but it looks like a mess to deal with compared to mstsc because it looks primitive (an article I read yesterday tried to say this type of prompt is ALWAYS a phishing scam which obviously it's not but try telling management), it doesn't auto-populate domains, and I get a WinRM error which I'm sure will be a rabbit hole.
So is it possible to RDP with mstsc and then pipe commands to it so they're executed on that computer?
The answer is no. You cannot initiate some kind of pipe using MSTSC.exe.
You can, however, use PSRemoting to send the command like you're trying to do already:
Invoke-Command -ComputerName '<FQDN>' -ScriptBlock {
New-Item -Path "$HOME\Desktop\Success.txt" -ItemType File
}
If you don't know the FQDN, then look up the IP using DNS:
[System.Net.Dns]::GetHostEntry('104.209.198.181')
All this failing.. you can fall back on WMI, but you don't get any console feedback:
$WmiArgs = #{
'Class' = 'Win32_Process'
'Name' = 'Create'
'ArgumentList' = 'powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "New-Item -Path $HOME\Desktop\Success.txt -ItemType File"'
'ComputerName' = '104.209.198.181'
}
Invoke-WmiMethod #WmiArgs
While I tested the above is working, you can shorthand even this!
([wmiclass]'\\104.209.198.181\root\cimv2:win32_process').
Create('powershell -NoP -NonI -W Hidden -C "New-Item -Path $HOME\Desktop\Success.txt -ItemType File"')
With this method, however, you cannot pass credentials.

Run a powershell script from one computer to the next

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
}

writing a powershell script to relay commands over two servers

I have been working on getting a script running that runs from a main domain controller. I want to be able to run the script from this domain controller and have it invoke some commands on a varied amount of remote servers which in turn copies files from a NAS on the same network to a local directory on each server. I use credentials from a securecard that I pass in to elevate privileges but I cannot seem to get it to copy the files down unless I execute the commands on the server itself. If I can get powershell to remotely execute these commands and perform the action then I would be all set.
Here is what I have come up with so far, please let me know what might help get this going for me. I would appreciate any help provided.
# Sets the variable for the source file location (c:\temp\ThisFile.exe)
#$Source = "\\nas\Applications\devapp\autononomous\v2\.exe"
$Source = "c:\temp\redirect"
# Sets the variable for the destination file location
$Destination = "temp"
# Sets the variable for application
$Application = "REDIRECT"
# Sets the variable for the remote destination file location
$Remote_Destination = "c:\$Destination\$Application"
# Sets the variable for the remote command to be executed
$Remote_Command = "regedit /s $Remote_Destination"
# Pass in the credentials you will be using to be able to copy from
one server to another, otherwise it will attempt to use local credentials.
$Credentials = get-credential
# Read in servers from text file and execute script
$Servers = Get-Content c:\temp\PowerShell\Servers.txt | ForEach-Object {
Write-Host "Installing" $Application "on server" $_
Copy-Item -Path $Source -Destination \\$_\c$\$Destination -recurse -force -Credential $Credentials
Invoke-Command -ComputerName $_ -ScriptBlock {
Param($Remote_DestinationR, $Remote_CommandR)
cmd.exe /c if not exist "c:\app\v2" mkdir "C:\app\v2"
cmd.exe /c xcopy /E /I /Y "C:\temp\redirect\home" "C:\app\v2\home"
} -Argumentlist $Remote_Destination, $Remote_Command
}

Powershell PS Session script files with includes

So i'm trying to run a powershell script against a remote machine, using Enter-PSSession. I can connect and run a single script file and its contents no problem. However when i run this:
.\RemoteFile.ps1 "dev-web01" "builder" "test" "2.5.0.0" "Web" ".\stage_properties.xml"
As you can see there are 5 parameters to this script.
param (
[Parameter(Mandatory=$true)]
$Computername,
[Parameter(Mandatory=$true)]
$Username,
[Parameter(Mandatory=$true)]
$Password,
[Parameter(Mandatory=$true)]
$Version,
[Parameter(Mandatory=$true)]
$Packagenames,
[Parameter(Mandatory=$true)]
$Propfile
)
$securePassword = ConvertTo-SecureString -AsPlainText -Force $Password
$cred = New-Object System.Management.Automation.PSCredential $Username, $securePassword
Enter-PSSession -ComputerName $Computername -Credential $cred
Invoke-Command -ComputerName $Computername -Credential $cred -FilePath
.\DeploymentRun.ps1 -ArgumentList $Propfile -EnableNetworkAccess
When it gets to running the Invoke-Command with the $Propfile argument (stage_properties.xml) , it's looking on the remote machine since, i'm assuming the location context has changed, instead of the same local directory where RemoteFile.ps1 and DeploymentRun.ps1 reside. Here is the error I receive:
Cannot find path 'C:\User\builder\Documents\stage_properties.xml' because it does not exist.
How do i get around this? I've done a bit of searching, but can't seem to find a solution.
Any advice is greatly appreciated.
It is as you described; the the file name you're passing is a path relative to the local machine, so the code running on the remote machine cannot find a file in that path.
There isn't a way to get "around" it as you're just running code on the remote machine. The results are serialized and sent back to the calling client.
Your best bet is probably to send the file contents as a parameter instead of the file itself. Then of course the target script must be modified to deal with that.
You could also pass the path as a UNC path to a share on the local machine, perhaps even an administrative share, like \\localmachine\c$\users\builder\documents\stage_properties.xml. But there's a chance you'll run into authentication issues (double hop).
(Un)related question:
Why are you using Enter-PSSession and then using Invoke-Command?
Edit based on your comments:
First, if you want to make multiple calls in a single session to a remote machine, first create a PSSession:
$session = New-PSSession -ComputerName $ComputerName
Then use that session in all subsequent calls:
Invoke-Command -Session $session -File $filename
Invoke-Command -Session $session -ScriptBlock {
# Some code
}
Then close the session when you're done:
Remove-PSSession -Session $session
Note that Enter-PSSession is purely for interactive uses. You generally wouldn't use it inside a script. But it too takes a -Session parameter that can use a previously created session.
As to the issue of calling multiple files, you have a few pickles here. First, let's remember that when you call Invoke-Command with the -File parameter, it's the same as if you read the contents of the file yourself, made a script block out of it, and passed it to the script block.
All of those commands are executed on the remote side in the context of a process on the remote side. So if those scripts refer to any resources, including other scripts, modules, etc., those resources must be access from the context of the remote execution.
Based on that, you could in theory put all of these files on a share, accessible to both sides, and use UNC paths.
In practice, you have a different issue there: The so-called "double hop" authentication problem which prevents a remote machine from delegating your credentials. This means that once you've authenticated to the remote machine, you can't then pass off your credentials to a third machine (like a file server hosting a UNC share).
In most of the articles out there about this (including the one I linked) they'll talk about "enabling" multi-hop through the use of CredSSP. It's easy enough to enable CredSSP without fully understanding it. In my opinion, it's not a good solution generally; in most cases you want to re-think how you're doing things.
Back to this case:
If your script files didn't need to call or reference other script files, then you could easily just do multiple Invoke-Command calls, executing each one in sequence.
You touched upon a possible solution to your issue: copy all of the files to the target machine first. You just have to make sure that the paths used in the scripts will work (not an issue if you're passing it as a parameter!).
Another thing you can do: don't execute the code remotely! Just run it locally.
But I imagine that's not possible otherwise you wouldn't have tried remoting, so what you can do instead is create a scheduled task (or series of tasks) on the remote machine (they can be on-demand tasks only).
You could then reduce your remote calls to something like:
Invoke-Command -Session $session -ScriptBlock {
# PowerShell 3.0+
Start-ScheduledTask -TaskName 'My Special Task'
# Any version of PS
schtasks.exe /RUN /TN "My Special Task"
}