Powershell PS Session script files with includes - powershell

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"
}

Related

How to exit and remove Pssession without prompt for session number [duplicate]

On my local PC and locally on the servers I admin, I regularly use the $profile script to set/output basic information. For instance running Set-Location to set the current path to the folder containing the scripts, and perhaps some Write-Host entries to show a basic cheat sheet for the most commonly used scripts and their expected parameters.
Does anyone know of a way to do something similar to that when using Enter-PSSession to connect interactively with a remote server?
As far as I can see there are no $profile files available with remote sessions, so I can't just add the commands in there (and the $profile used interactively on the local server doesn't get called when you remote into that same server).
Locally I've added functions to my local profile to make connecting to specific servers quicker, for example :
function foo{
$host.ui.RawUI.WindowTitle = "Foo"
Enter-PSSession -computername foo.local.mydomain.com -authentication credssp -credential mydomain\adminuser
}
and that works fine for connecting me (eg I type foo, then enter my password, and I'm in), but I still get dumped into C:\Users\adminuser\Documents.
I've tried adding things like the Set-Location command to the function after the connection, but that gets run in the local context (where the folder doesn't exist) and THEN it connects to the server. I even tried piping the commands to Enter-PSSession, but perhaps unsuprisingly that didn't work either.
Obviously things like Invoke-Command would allow me to specify commands to run once connected, but that wouldn't (as far as I can work out) leave me with an interactive session which is the primary aim.
You can't really automate unattended execution of anything that happens after Enter-PSSession connects your host to the remote session - but you can execute all the code you want in the remote session before calling Enter-PSSession:
function DumpMeInto {
param([string]$Path)
# Create remote session (you'll be prompted for credentials at this point)
$session = New-PSSession -ComputerName foo.local.mydomain.com -Authentication credssp -Credential mydomain\adminuser
# Run Set-Location in remote runspace
Invoke-Command -Session $session -ScriptBlock { Set-Location $args[0] } -ArgumentList $Path
# ... and then enter the session
Enter-PSSession -Session $session
}
Now you can do DumpMeInto C:\temp and it should drop you into a remote session on foo.local.mydomain.com with it's working directory set to c:\temp

Enter-PSSession equivalent of $profile script

On my local PC and locally on the servers I admin, I regularly use the $profile script to set/output basic information. For instance running Set-Location to set the current path to the folder containing the scripts, and perhaps some Write-Host entries to show a basic cheat sheet for the most commonly used scripts and their expected parameters.
Does anyone know of a way to do something similar to that when using Enter-PSSession to connect interactively with a remote server?
As far as I can see there are no $profile files available with remote sessions, so I can't just add the commands in there (and the $profile used interactively on the local server doesn't get called when you remote into that same server).
Locally I've added functions to my local profile to make connecting to specific servers quicker, for example :
function foo{
$host.ui.RawUI.WindowTitle = "Foo"
Enter-PSSession -computername foo.local.mydomain.com -authentication credssp -credential mydomain\adminuser
}
and that works fine for connecting me (eg I type foo, then enter my password, and I'm in), but I still get dumped into C:\Users\adminuser\Documents.
I've tried adding things like the Set-Location command to the function after the connection, but that gets run in the local context (where the folder doesn't exist) and THEN it connects to the server. I even tried piping the commands to Enter-PSSession, but perhaps unsuprisingly that didn't work either.
Obviously things like Invoke-Command would allow me to specify commands to run once connected, but that wouldn't (as far as I can work out) leave me with an interactive session which is the primary aim.
You can't really automate unattended execution of anything that happens after Enter-PSSession connects your host to the remote session - but you can execute all the code you want in the remote session before calling Enter-PSSession:
function DumpMeInto {
param([string]$Path)
# Create remote session (you'll be prompted for credentials at this point)
$session = New-PSSession -ComputerName foo.local.mydomain.com -Authentication credssp -Credential mydomain\adminuser
# Run Set-Location in remote runspace
Invoke-Command -Session $session -ScriptBlock { Set-Location $args[0] } -ArgumentList $Path
# ... and then enter the session
Enter-PSSession -Session $session
}
Now you can do DumpMeInto C:\temp and it should drop you into a remote session on foo.local.mydomain.com with it's working directory set to c:\temp

Using PowerShell to execute a remote script that calls a batch file

I am having an issue with running a batch file that is located on a remote server.
I have a batch file located on a remote server that i want to run that kicks off an automated Selenium test. For simplicity, let's say the name of my batch file is mybatch.bat
I have the following code in a Powershell script located on the server:
$BatchFile = "mybatch.bat"
Start-Process -FilePath $BatchFile -Wait -Verb RunAs
If I run this PowerShell script locally on the server in ISE then it runs fine and it kicks off the selenium test which takes a couple minutes to run.
Now I want to try to execute this test from another machine by using PowerShell remoting. Let's assume that remoting is already configured on the servers.
I have a PowerShell script located on another server which has the following code segment. Assume that all of the session variables have the correct information set:
$CMD = "D:\mybatch.bat"
$TargetSession = New-PSSession -ComputerName $FullComputerName -Credential $myCreds -ConfigurationName RemoteExecution
$command = "powershell.exe -File $CMD -Wait"
Invoke-Command -Session $TargetSession -ScriptBlock { $command }
When this script runs, it does connect to the remote machine and create a remote session. It does look like it kicks off the batch file because PowerShell does not give me an error. But it does not wait for the full 3 or 4 minutes for the Selenium test to finish. It seems like it just times out. Also if I am logged onto the other machine, I don't see any Selenium web test running. No Selenium log files or results files are created on remote server as should be expected.
I was wondering what I could be doing wrong with my code.
Also, it seems that the server always returns the echo outputs of the batch file to my local machine. I see these random blinking white screen on ISE which looks like output from the batch file
$command = "powershell.exe -File $CMD -Wait"
Invoke-Command -Session $TargetSession -ScriptBlock { $command }
There are 2 issues with the above code:
$command inside the scriptblock and $command outside the scriptblock are different variables due to different scopes. The variable inside the scriptblock is thus undefined and the scriptblock will simply echo an emtpy value.
Even if $command weren't undefined, the scriptblock would still just echo its value, since it's defined as a string. PowerShell does not execute strings unless you're using something like Invoke-Expression (which you shouldn't).
This should do what you want:
$CMD = "D:\mybatch.bat"
$TargetSession = New-PSSession -ComputerName $FullComputerName -Credential $myCreds -ConfigurationName RemoteExecution
Invoke-Command -Session $TargetSession -ScriptBlock { & $using:CMD }
If you would like execute a bat file from another machine by using PowerShell Remote Session, simply enter dot and then follow by a whitespace, then enter the exact path of bat file located on that remote machine.
Example
Invoke-Command -ComputerName RemoteComputerName -Credential $credential -ScriptBlock {. "D:\Temp\Install_Something.bat"}

Powershell Remoting: Load local script inside script

I launch a local script on a remote computer with:
Invoke-Command -ComputerName RemoteServer -FilePath C:\myFolder\Script1.ps1
This script dot-sources another script (Script2), which also is located on my local computer in "C:\myFolder". This fails because Script1 trys to load the Script2 from the remote computer.
Is there a way to load Script2 from my local computer inside Script1 inside the remoting session?
[Workaround]
Create a session to the remote server
Use the session to load the script2
use the same session to run the script1.
eg:- $Session = New-PSSession -ComputerName $Server
Invoke-command -Session $Session -FilePath <script2> this should load functions
Invoke-command -Session $Session -FilePath <script1>
here the functions in script2 will be available for script1 to consume, so no need to refer and dot source script2 .
I don't think this can work the way you outlined it since code running on the remote machine cannot find a file that is relative to the local machine.
The only way to achieve this is to share your script and include it as with a UNC path, or an administrative share, like \\localmachine\c$\users\test\script\a.ps1.
But there's a chance you'll run into authentication issues (double hop).
The (easiest) solution: copy all your scripts to the remote machine first and make sure that the paths used in the scripts will work.

Copy/transfer file using PowerShell 2.0

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>