PowerShell Error - Missing expression after unary operator '-' - powershell

I have two PowerShell scripts. One is for uninstalling and installing a SharePoint 2010 solution on the SharePoint server. The other calls this script with two commands, one for the uninstall and one for the install. These are not scripts I wrote, but I have inherited them.
Here is the script that calls the install / uninstall script (I've removed some parameters to simplify for troubleshooting):
& 'C:\Users\username\Documents\setup.ps1' -InstallOrUninstall '/UNINSTALL'
& 'C:\Users\username\Documents\setup.ps1' -InstallOrUninstall '/INSTALL'
Stripped down to almost nothing for the purpose of testing, here is the "setup.ps1" script:
param ($InstallOrUninstall,
$SiteURL,
$WebURL,
[switch]$ignoreFeatures,
[switch]$thisAppDomain )
if (-not $thisAppDomain)
{
Write-Host "Invoking script in a new app domain" -foregroundcolor yellow
Write-Host $MyInvocation.Line
powershell.exe -Version 2 -Command $MyInvocation.Line -thisAppDomain
return;
}
Write-Host "In Body"
Write-Host $MyInvocation.Line
Running the first script returns an error from the first command, but not from the second. The error is:
powershell.exe : - : Missing expression after unary operator '-'.
At C:\Users\username\Documents\setup.ps1:11 char:6
+ powershell.exe -Version 2 -Command $MyInvocation.Line -thisAppDo ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (- : Missing exp...y operator '-'.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
+ CategoryInfo : ParserError: (-:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingExpressionAfterOperator
I believe the reason the install / uninstall script is resending the command for "Version 2" is because of an issue with using PowerShell past Version 2 with SharePoint 2010 (as outlined here). However, I don't understand why only the first line fails. The second command also enters the if statement, but does not error.
If I remove the second line, and only call the setup.ps1 one time, the script that calls the install / uninstall script succeeds.

A nice little brain-teaser. Apparently, $MyInvocation.Line contains the full line, including the newline / linebreak at the end. So, -thisAppDomain is not interpreted as a parameter, but the beginning of a new expression starting with -. This is also, why it works if you remove the 2nd line, because then you do not have a line break at the end.
To reproduce this error, try:
powershell.exe -Version 2 -Command "`r`n-thisAppDomain"
Note that in newer versions the parsing algorithm was obviousy modified and the error message is different. Omit the -Version 2 switch and you likely get:
The term "-thisAppDomain" is not recognized as the name of a cmdlet, function, script file, or operable program...
One simple way to resolve this would be to .Trim() (or .TrimEnd()) the command:
powershell.exe -Version 2 -Command $MyInvocation.Line.Trim() -thisAppDomain
I need to add though, that you should reconsider what your actual problem is, and if your solution is actually the best way to solve it. Have a look at jobs for example.

As marsze said: It is not interpreted as a parameter, but the beginning of a new expression starting with -. This is also, why it works if you remove the 2nd line, because then you do not have a line break at the end.

Related

Uninstall program using get-process and uninstallstring with call operator (&)

I'm trying create a uninstall script to uninstall any program tha I want.
I was using this script:
get-package -Name "XXXXX" | Uninstall-Package
But I saw that when the "provideName" attribut is "Program" it not working, so I use this.
$packageArray = get-package "*XXXX*"
foreach ($package in $packageArray){
$a = $package.ProviderName
if ($a -eq "Programs"){
& {($package.Meta.Attributes["UninstallString"] -replace '"') /S}
}
if ($a -ne "Programs"){
get-package -Name "*XXXXX*" | Uninstall-Package
}
}
This part bellow was working fine when I have not ""&quot."", like this.
& {($package.Meta.Attributes["UninstallString"] -replace '"') /S}
Print:
But now I'm getting erro when a uninstall string has "&quot." value, like this.
UninstallString=""&quot";C:\Users\rsantanna\AppData\Local\GoToMeeting\19950\G2MUninstall.exe" /uninstall"
Print.
When it occur I get this error.
& : The term 'C:\Users\rsantanna\AppData\Local\GoToMeeting\19950\G2MUninstall.exe /uninstall' 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.
At line:6 char:7
& ($UninstallString)
~~~~~~~~~~~~~~~~~~
CategoryInfo : ObjectNotFound: (C:\Users\rsanta....exe /uninstall:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
Anyone can help me ?
You cannot directly execute a string expression such as ($package.Meta.Attributes["UninstallString"] -replace '"')
While you can do so via &, the call operator, it only works if the string expression evaluates to a command name or executable file path only.
Notably, you can not use & to execute an entire command line, which is what $package.Meta.Attributes["UninstallString"] typically contains.
Note that your use of & is with a script block ({ ... }), which allows you to execute arbitrary statements contained in the block; however, perhaps needless to say, these statements are subject to the usual syntax rules, and it is the one and only statement in your block that causes a syntax error (that is actually different from the error you report).
As explained in detail in this answer, the command lines stored in UninstallString registry values are designed for invocation from cmd.exe or from no-shell environments.
Therefore, the simplest solution is to call via cmd.exe /c:
cmd.exe /c "$($package.Meta.Attributes["UninstallString"]) /S"
If your uninstall command line, as returned from $package.Meta.Attributes["UninstallString"], really contains " in lieu of verbatim " characters (double quotes) - which would be unusual - you'll need to replace the former with the latter first:
cmd.exe /c "$($package.Meta.Attributes["UninstallString"] -replace '"', '"') /S"
It's unclear what command produced the " output in your screenshot, but given that it shows the text of an XML document, the use of " instead of " is simply a necessity for encoding literal " chars.

Set window title when running "start powershell" in powershell not working?

The following command works in CMD (How to start powershell with a window title?).
start powershell -NoExit -command "$Host.UI.RawUI.WindowTitle = 'bits'"
But it doesn't work in Powershell.
PS C:\> start powershell -noexit -command "$Host.UI.RawUI.WindowTitle = 'test'; read-host"
Start-Process : A parameter cannot be found that matches parameter name 'noexit'.
At line:1 char:18
+ start powershell -noexit -command "$Host.UI.RawUI.WindowTitle = 'test ...
+ ~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Start-Process], ParameterBindingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.StartProcessCommand
The following command can open a new powershell window.
start powershell "$Host.UI.RawUI.WindowTitle = 'test'; read-host"
However, the new window shows the following error message and the title is not set.
System.Management.Automation.Internal.Host.InternalHost.UI.RawUI.WindowTitle : The term
'System.Management.Automation.Internal.Host.InternalHost.UI.RawUI.WindowTitle' 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.
At line:1 char:1
+ System.Management.Automation.Internal.Host.InternalHost.UI.RawUI.Wind ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.Manageme...wUI.WindowTitle:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Bacon Bits' helpful answer explains that start in cmd.exe means something different than in PowerShell.
Use Start-Process as follows to get the desired result; note that powershell implicitly binds to parameter -FilePath, whereas the ,-separated arguments starting with -NoExit bind implicitly to the -ArgumentList (-Args) parameter, which accepts an array of strings:
# In PowerShell, `start` is an alias for `Start-Process`
start powershell '-NoExit', '-command', "`$Host.UI.RawUI.WindowTitle = 'bits'"
In paticular, --prefixed pass-through arguments must be quoted so that they're not mistaken for Start-Process's own parameters.
Also note the ` preceding the $ in $Host, which prevents up-front interpolation of $Host by the calling PowerShell instance.
You could also use '$Host.UI.RawUI.WindowTitle = ''bits''', a single-quoted literal string with embedded single quotes escaped as ''.
Important:
While passing arguments as an array to -ArgumentList is conceptually the best approach, it is unfortunately ill-advised due to a long-standing bug in Start-Process, still present as of this writing (v7.1) - see GitHub issue #5576.
For now, using a single string comprising all arguments, enclosed in embedded "..." quoting as necessary, is the only generally robust approach. As discussed in the linked GitHub issue, an -ArgumentArray parameter that supports robust array-based argument passing may be introduced in the future.
In the case at hand this means the following, as suggested by PetSerAl in a comment on the question:
Start-Process powershell '-NoExit -command "$Host.UI.RawUI.WindowTitle = ''bits''"'
Note the single-quoting_ ('...') of the overall argument-list string, which then necessitates escaping the embedded single quotes - those that PowerShell should see as part of the command - as ''.
In Command Prompt, start is the start internal command. In Windows Powershell, start is an alias for Start-Process, which does something similar but isn't identical.
Try running this:
powershell -NoExit -command "`$Host.UI.RawUI.WindowTitle = 'bits'"
Here's another way, while avoiding the dollar sign. The double quotes have to be on the outside, so that bits stays quoted.
start powershell "-noexit (get-variable host).value.ui.rawui.windowtitle = 'bits'"
You can always avoid these quoting issues putting the command in a file.
start powershell '-noexit .\window.ps1'
start powershell with the command option for setting the window title did not work for me. Maybe because I want to open another powershell with a ps1 file. so in the other ps1 file I added the first line as below,
(Get-Host).ui.RawUI.WindowTitle='TEST TEST'
and it worked like a charm....
If you add this to your powershell profile.ps1 you can get the window title to show the current running script and if you are just opening a window with no script then 'pwsh' will be displayed.
Will be systematic with no need to add a line on top of each script. The other answers
combined with $MyInvocation.MyCommand seem to give the name of the profile.ps1 instead when running a script from the context menu.
This can also be tweaked to change the result.
[console]::title = Split-Path -Leaf ([Environment]::GetCommandLineArgs()[-1]).Replace('pwsh.dll','pwsh')
Works on both PS 5 and 7 . For ver. 5 replace pwsh.dll by powershell.exe

Script runs in ISE but not in Powershell

I have a powershell script. This script runs perfectly fine if I open it in Powershell ISE, however, if I right click on the file and click 'run with powershell' the script throws an error.
Furthermore, I read in previous threads that the following execution pattern solved the issue for some people:
powershell -STA -File script.ps1
In this case this didn't solve the issue, however it did allow me to read the error:
At C:\Users\sancarn\AppData\Local\Temp\script.ps1:20 char:20
+ $parent = [System.Windows.Forms.TreeNode]$global:database.Ite ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.TreeNode].
At C:\Users\sancarn\AppData\Local\Temp\script.ps1:27 char:36
+ ... [void]$node.nodes.add([System.Windows.Forms.TreeNode]::new(" ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.TreeNode].
At C:\Users\sancarn\AppData\Local\Temp\script.ps1:33 char:45
+ ... PSCustomObject]IWDBGetChildren([System.Windows.Forms.TreeNode]$node) ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Unable to find type [System.Windows.Forms.TreeNode].
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TypeNotFound
This having been said, I'm not sure I can really do anything about this error specifically... I already load System.Windows.Forms and System.Drawing... Does anyone have any idea how to execute this file properly?
Edit
Other attempts at trying to fix the issue:
powershell -NoExit -STA -File script.ps1
powershell -NoExit -STA -File script.ps1 -Scope Global
Edit 2
I have also tried adding:
Add-Type -AssemblyName System.Windows.Forms
To the top of the powershell script. The issue remains unresolved however.
Edit:
Not sure why this is being flagged as a duplicate after
this answer already has a recommended answer and
the recommended answer demonstrates why this is different.
...
As #PetSerAl said in his comment, the answer is in https://stackoverflow.com/a/34637458
Every PowerShell script is completely parsed before the first statement in the script is executed. An unresolvable type name token inside a class definition is considered a parse error. To solve your problem, you have to load your types before the class definition is parsed, so the class definition has to be in a separate file.
Ultimately to resolve the issue I need to either load my class definitions as a separate file, or store my declarations in a header file, which calls the class definitions (and the rest of the script).
I am amazed this was even an issue but it works now, so that's good...
Edit
In my case it is a bit different to the post linked by the solution author. I'm actually building a ruby library for executing Powershell on windows machines. Currently I don't write data to a file and I can't guarantee that a here-string/invoke-expression will work.
Invoke-Expression #"
$anotherString=#"
hello world!
"#
"# <--- Powershell will throw an error here!
Instead I've decided to encode the body if a header is present, and then decode and execute at runtime
require 'base64'
if header!=""
encoded_body = Base64.strict_encode64(body.encode("utf-16le"))
body = <<-END_TRANSFORMATION
#{header}
$encoded_body = "#{encoded_body}"
Invoke-Expression $([System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($encoded_body)))
END_TRANSFORMATION
end
So far this has appeared to work in all cases, doesn't need external files, and will work even if users use here-doc strings in their Powershell scripts!

Environment variable, PowerShell session vs. started from CMD

I am trying to run a PowerShell command from within a command prompt window (run as Administrator), but it fails. Whereas when I run the same command from within a PowerShell window it runs fine.
Here is the command without error in a PowerShell window:
Powershell [Environment]::SetEnvironmentVariable("HostIPv4", "192.168.255.14:", "Machine")
In the command prompt window it fails:
C:\test>powershell [Environment]::SetEnvironmentVariable("HostIPv4", "192.168.255.14:", "Machine")
At line:1 char:39
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~
Missing ')' in method call.
At line:1 char:39
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~~~~~~~~
Unexpected token 'HostIPv4' in expression or statement.
At line:1 char:47
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~
Missing argument in parameter list.
At line:1 char:73
+ ... ironment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Machine)
+ ~
Unexpected token ')' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInMethodCall
What could be the problem?
PowerShell's command-line parsing removes the double quotes, try using single quotes:
powershell [Environment]::SetEnvironmentVariable('HostIPv4', '192.168.255.14:', 'Machine')
Also note that you have to reopen an new process window to see the results (this is a known behavior for the command shell, see also: C# set environment variable)
iRon's helpful answer explains the problem and works, but I suggest adopting a generally more robust approach to invoking PowerShell commands from cmd.exe:
Use -Command explicitly, because in PSv6 the default will change to -File, expecting a script filename rather than a command.
Use -NoProfile, to avoid unnecessary loading of the PowerShell profiles and for a more predictable execution environment.
Double-quote your entire PowerShell command to protect it from potentially unwanted up-front interpretation by cmd.exe.
powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('HostIPv4', '192.168.255.14:', 'Machine')"
The use of ' instead of " inside the command string is an easy way to avoid having to escape embedded " characters, which works fine here, but in case you do need embedded " (for interpolating strings), escape them either as \" (sic) or """ (sic).

powershell cmdlet errors vary in script vs. console direst entry

Please understand this is a part of a much larger script.
It is unreasonable for me to post the entire script to this question.
I have done a bunch of test and proven the root cause is the Copy-Item command ~ behaves differently run as a script vs. manually executing the cmdlet manually typing in the PS console.
#PS 5.1.14393.206 on Windows10 properly
$SiteName = "mysite"
$AppPoolTemplate = ".NET` v4.5"
$iisAppPoolTemplate = "IIS:\AppPools\$AppPoolTemplate"
$iisAppPool = "IIS:\AppPools\$SiteName"
$iisSites = "IIS:\Sites\$SiteName"
Write-Host "AppPoolTemplate //$AppPoolTemplate//"
Write-Host "iisAppPoolTemplate //$iisAppPoolTemplate//"
Write-Host "iisAppPool //$iisAppPool//"
Write-Host "iisSites //$iisSites//"
Stop-WebAppPool -Name $AppPoolTemplate
Copy-Item $iisAppPoolTemplate $iisAppPool # fails
Copy-Item IIS:\AppPools\.NET` v4.5 IIS:\AppPools\mysite # doesn't fail, but doesn't copy either
The first Copy-Item statement
Copy-Item $iisAppPoolTemplate $iisAppPool
causes the script to fail. Removing this line proves the problem starts with this line. The actual error line reported is whichever is the last line with properly "closed string literal". Erroneously reporting the literal is not closed. This error only occurs if I run the script. If I cut-and-paste the script content into PS console ... no error.
If run the second Copy-Item statement
Copy-Item IIS:\AppPools\.NET` v4.5 IIS:\AppPools\mysite
from a script it completes without error. But doesn't do what it is supposed to do. If I cut-and-paste the above command into a PS console .. no error AND does what it is supposed to do.
Here is the actual error.
The error line #87 is the last line in the script with a string literal.
Line #67 also has a string literal. If I remove line #87 the error becomes the same only now on line #67.
The Copy-Item line sets up the problem which is only reported at the last closed string literal closest to the end of the script.
PS C:\scripts> .\_addSite.ps1
At C:\scripts\_addSite.ps1:87 char:51
+ $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
+ ~~
The string is missing the terminator: ".
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
After posting this question, I took the boiled down script I wrote for this question and found it wasn't creating the error. So I slowly cut-and-paste little sections from the broken script into this question's version. Then re-ran the question's script each time. And always without error.
What changed? I closed and re-opened the PS console between giving up on a broken script and starting on this question. And now neither the question's script nor the broken version have a problem.
Probable solution? There was something in the previous PS console environment that was the root cause and this was flushed out by closing the console. How lovely.
Thanks everyone for your remarks