PowerShell 2.0 - Running scripts for the command line call vs. from the ISE - command-line

After writing deployment scripts from within the ISE, we need our continuous integration (CI) server to be able to run them automatically, i.e. from the command line or via a batch file.
I have noticed some significant differences between the following calls:
powershell.exe -File Script.ps1
powershell.exe -Command "& '.\Script.ps1'"
powershell.exe .\Script.ps1
Some simple examples:
When using -File, errors are handled in the exact same way as the ISE.
The other two calls seem to ignore the $ErrorActionPreference variable, and do not catch Write-Error in try/catch blocks.
When using pSake:
The last two calls work perfectly
Using the ISE or the -File parameter will fail with the following error:
The variable '$script:context' cannot be retrieved because it has not been set
What are the implications of each syntax, and why they are behaving differently? I would ideally like to find a syntax that works all the time and behaves like the ISE.

Not an answer, just a note.
I searched for explanation of -file parameter. Most sources say only "Execute a script file.". At http://technet.microsoft.com/en-us/library/dd315276.aspx I read
Runs the specified script in the local scope ("dot-sourced"), so that the functions
and variables that the script creates are available in the current session. Enter
the script file path and any parameters.
After that I tried to call this:
powershell -command ". c:\temp\aa\script.ps1"
powershell -file c:\temp\aa\script.ps1
powershell -command "& c:\temp\aa\script.ps1"
Note that first two stop after Get-Foo, but the last one doesn't.
The problem I describe above is related to modules -- if you define Get-Foo inside script.ps1, all the 3 calls I described stop after call to Get-Foo.
Just try to define it inside the script.ps1 or dotsource the file with Get-Foo and check it. There is a chance it will work :)

Here is a concrete example of the behaviour I described.
MyModule.psm1
function Get-Foo
{
Write-Error 'Failed'
}
Script.ps1
$ErrorActionPreference = 'Stop'
$currentFolder = (Split-Path $MyInvocation.MyCommand.Path)
Import-Module $currentFolder\MyModule.psm1
try
{
Get-Foo
Write-Host "Success"
}
catch
{
"Error occurred"
}
Running Script.ps1:
From the ISE, or with the -File parameter
will output "Error occurred" and stop
From the command line without the -File parameter
will output "Failed" followed by "Success" (i.e. not caught)

Related

PowerShell function not running as expected

I have a curious case that I cannot fathom the reason for...
Please know I am a novice to PowerShell.
I am working on a PowerShell menu system to help automate building out new computers in my environment. I have a PS1 file that holds the script for an app install. When I use the script to reference this I am able to run it and have no issue. However, when I try inserting this into a function and referencing it does not.
This works:
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
# {installMS32Bit}
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
Start-Sleep -seconds 2
}
This does not:
function installMS32Bit(){
Invoke-Expression "cmd /c start powershell -NoExit -File '\\**SERVERPATH**\menuItems\ms_office\32-bit\install.ps1'"
}
}
4 # Microsoft Office 32-bit
{
Write-Host "`nMicrosoft Office 32-bit..." -ForegroundColor Yellow
{installMS32Bit}
Start-Sleep -seconds 2}
install.ps1 file:
# Copy MS Office uninstall and setup to local then run and install 32-bit Office
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\setup.exe' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\uninstall.xml' -Destination 'C:\temp\' -Force
Copy-Item -Path '\\**SERVERPATH**\menuItems\ms_office\32-bit\Setup.exe' -Destination 'C:\temp' -Force
Invoke-Expression ("cmd /c 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'")
Start-Process -FilePath 'C:\temp\Setup.exe'
Secondary question and a little explanation for Invoke-Expression...
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
If there is a better way to do this in PowerShell I am all ears!
{installMS32Bit}
As Mathias points out in a comment on the question, this statement doesn't call your function, it wraps it in a script block ({ ... })[1], which is a piece of reusable code (like a function pointer, loosely speaking), for later execution via &, the call (execute) operator.
To call your function, just use its name (by itself here, given that there are no arguments to pass): installMS32Bit
Invoke-Expression should generally be avoided; definitely don't use it to invoke an external program, as in your attempts.
Additionally, there's generally no need to call an external program via cmd.exe (cmd /c ...), just invoke it directly.
For instance, replace the last Invoke-Epression call from your question with:
# If the EXE path weren't quoted, you wouldn't need the &
& 'C:\temp\setup.exe' /configure 'C:\temp\uninstall.xml'
I like to see progress and like to have secondary windows open to monitor the new process being run. I was unable to find a solution with a persistent window that worked for me to do this without Invoke-Expression.
(On Windows), Start-Process by default executes a console application in a new window (unless you specify -NoNewWindow), asynchronously (unless you specify -Wait).
You cannot pass a .ps1 script directly to Start-Process (it will be treated like a document to open rather than an executable to call), but you can pass it to PowerShell's CLI via the -File parameter:
Start-Process powershell.exe '-File install.ps1'
The above is short for:
Start-Process -FilePath powershell.exe -ArgumentList '-File install.ps1'
That is, PowerShell will execute the following in a new window:
powershell.exe -File install.ps1
[1] Since you're not assigning the script block being created to a variable, it is implicitly output (printed to the display, in the absence of a redirection); a script block stringifies by its literal contents, excluding the enclosing { and }, so string installMS32Bit will print to the display.

Write-output when executing script in a wpf event script

I have a script that accepts lots of parameters and is fairly big. I've been asked to build a GUI for executing the script, so users can easily execute the script without making mistakes. So the idea was that the GUI script collects all the parameters, and then executes the script.
The "main" script uses write-output and start-transcript for logging to the shell and to a file. This also works.
But when I execute the script from the GUI script I'm not getting every output to the shell or to the log. I figured that this is because of write-output because Write-Host does work, but everywhere I go I hear people say that you shouldn't use write-host (for instance: https://youtu.be/SSJot1ycM70?t=24m1s).
So how do I get this to work?
Currently I use this to execute the script from the gui:
& $PSscriptroot\guitest2.ps1 -switchparam1:$localvar1 -switchparam2:$localvar2 -stringparam $localvar3
I have tried to run a new instance of powershell via cmd, but I don't seem to get this to work. I have also no idea how to "send" the parameters to the script this way.
Invoke-Expression 'cmd /c powershell.exe -file C:\...\script.ps1 -paramters.
or
Invoke-Expression 'cmd /c powershell.exe -command {C:\..\script.ps1 -paramters..}
The two scripts below do exactly what I want. But when I execute the script in a wpf event (ie after the start button has been pressed) the second script only shows the write-warning, write-error and write-host output, so not the write-output.
script1:
#gui script
$boolean = $true
$string = "test"
& $PSscriptroot\script2.ps1 -switch:$boolean -string $string
script2:
param(
[switch]$switch,
[string]$string
)
Start-Transcript
write-output "output"
write-host "host"
$a = "Variable"
$a
write-error "error"
Write-Warning "warning"
Write-Debug "debug"
Stop-Transcript
EDIT:
did some more testing. Apparently write-output completely doesn't work when called in a wpf event. For example:
$wpf.startButton.Add_Click({
write-output "This message is not shown"
})
Not sure if this is a bug or not
Write-Output passes an object through the pipeline to another command. Essentially the statement
Write-Output <expression>
is in many cases exactly identical to
<expression>
Other Write-* cmdlets behave a bit differently. In your case you have an event handler, which has no return value anyway. Your example is the same as
"This message is not shown"
or even
return "This message is not shown"
As the string is part of the return value of that script block, which no one ever looks at.
If you want output in the host application, use Write-Host. That's what it's for. Write-Output is something you rarely, if ever, need in a script.

parse error in one-line powershell script

I am trying to create a one-line powershell script that just requests an url. The script is working fine when I run it as a ps1 file:
File "test.ps1":
$webclient=New-Object "System.Net.WebClient"
$data=$webclient.DownloadString("https://google.com")
I run this script in PS console like this:
PS C:\test.ps1 -ExecutionPolicy unrestricted
This runs without any problem, but when I try to schedule this script and make it a one-line according to these recommendations i.e. replace "" with '' and separate commands with ; so the result will be:
one-line:
powershell -ExecutionPolicy unrestricted -Command "$webclient=New-Object 'System.Net.WebClient'; $data=$webclient.DownloadString('https://google.com');"
Then I got the following problem:
Error:
The term '=New-Object' is not recognized as the name of a cmdlet,
function, script file, or operable program
I tried another script that also works fine as ps1 file, but not working as one-liner:
$request = [System.Net.WebRequest]::Create("https://google.com")
$request.Method = "GET"
[System.Net.WebResponse]$response = $request.GetResponse()
echo $response
one-line:
powershell -ExecutionPolicy unrestricted -Command "$request = [System.Net.WebRequest]::Create('https://google.com'); $request.Method = 'GET'; [System.Net.WebResponse]$response = $request.GetResponse(); echo $response"
Error:
Invalid assignment expression. The left hand side of an assignment
operator needs to be something that can be assigned to like a variable
or a property. At line:1 char:102
According to get-host command I have powershell v 2.0. What is the problem with one-line scripts above?
Put the statements you want to run in a scriptblock and run that scriptblock via the call operator:
powershell.exe -Command "&{$webclient = ...}"
Note that pasting this commandline into a PowerShell console will produce a misleading error, because PowerShell (the one into which you paste the commandline) expands the (undefined) variables in the string to null values, which are then auto-converted to empty strings. If you want to test a commandline like this, run it from CMD, not PowerShell.
It might also be a good idea to have the scriptblock exit with a status code, e.g.
&{...; exit [int](-not $?)}
or
&{...; $status=$response.StatusCode.value__; if ($status -eq 200) {exit 0} else {exit $status}}

Capturing different streams in file

I'm trying to capture the Verbose, Error and other streams of a PowerShell script in a file. This to monitor the output of my script.
The following code works fine:
$LogFile = 'S:\ScriptLog.log'
$ScriptFile = 'S:\TestieScript.ps1'
powershell -Command $ScriptFile *>&1 > $LogFile
However, the moment I try to put a space in one of the file paths, it's no longer working. I tried a lot of things, like double quotes, single quotes, .. but no luck.
To illustrate, the following code doesn't work:
$LogFile = 'S:\ScriptLog.log'
$ScriptFile = 'S:\Testie Script.ps1'
powershell -Command $ScriptFile *>&1 > $LogFile
One person in this thread has the same issue.
Thank you for your help.
You're trying to run a file whose name contains a space as a command without proper quoting, so you're most likely getting an error like this in your log:
The term 'S:\Testie' is not recognized as the name of a cmdlet, function, script file, or operable program.
Either add proper quoting (and the call operator &, because your path is now a string):
powershell -Command "& '$ScriptFile'" *>&1 > $LogFile
or (better) use the -File parameter, as #CB. already suggested:
powershell -File $ScriptFile *>&1 > $LogFile
which has the additional advantage that the call will return the actual exit code of the script.
Edit: If you want to run the command as a scheduled task you'll need to use something like this:
powershell -Command "& 'S:\Testie Script.ps1' *>&1 > 'S:\ScriptLog.log'; exit $LASTEXITCODE"
because the redirection operators only work inside a PowerShell process.
try using -file parameter:
powershell -file $ScriptFile *>&1 > $LogFile

Executing powershell.exe from powershell script (run in ISE but not in script)

I'm new to these awesome Power shell world. I have a problem with script and really apreciate your help.
I have a script "cmd4.ps1" that needs to run another script "Transfer.ps1" that needs to receive 3 strings params and it needs to be run as other process thead different to "cmd4.ps1".
cmd4.ps1:
$Script="-File """+$LocalDir+"\Remote\Transfer.ps1"" http://"+$ServerIP+"/Upload/"+$FileName+" "+$ServerIP+" "+$LocalIP
Start-Process powershell.exe -ArgumentList $Script
After ejecution, the $Script cointain a value similar to
-File "c:\temp re\Remote\Transfer.ps1" http://10.1.1.1/Upload/file.txt 10.1.1.1 10.1.1.10
containing the syntax to use -File parameter to run a script of Powershell.exe, and three parameters that Transfer.ps1 needs ("http://10.1.1.1/Upload/file.txt", 10.1.1.1, 10.1.1.10).
When I write these instructions in PowerShell ISE I can see every values are right and PowerShell.exe is executed with right values, everything work fine!, but if I put these instructions inside "cmd4.ps1" script it doesn't work, I mean something is not right with parameters because I can see it start powershell but it never ends.
-ArgumentList is expecting an array of string arguments instead of a single string. Try this instead:
$ScriptArgs = #(
'-File'
"$LocalDir\Remote\Transfer.ps1"
"http://$ServerIP/Upload/$FileName $ServerIP $LocalIP"
)
Start-Process powershell.exe -ArgumentList $ScriptArgs
Note that you can simplify the string construction of the args as shown above.
Why don't you put this in cmd4.ps1?
& "c:\temp re\Remote\Transfer.ps1" "http://10.1.1.1/Upload/file.txt" "10.1.1.1" "10.1.1.10"