powershell cmdlet errors vary in script vs. console direst entry - powershell

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

Related

Why a powershell command is valid in the terminal but invalid from lnk file?

For example, this command works perfectly from a PowerShell prompt :
powershell.exe -NoExit -c Set-Variable -Name "CA_HOME" -Value "$(Get-Location).Path\intermed-ca"
but fails with an error if used from an lnk file.
Set-Variable : A positional parameter cannot be found that accepts argument '\intermed-ca'.
At line:1 char:1
+ Set-Variable -Name CA_HOME -Value $(Get-Location).Path'\intermed-ca'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Set-Variable], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetVariableCommand
Why ?
The error you supplied is not from that particular command. Notice PowerShell is showing us the command it is erroring on.
That particular error is simply a syntax error; that is not how you concatenate strings. Running just $(Get-Location).Path'\intermed-ca' will give you an error: "Unexpected token ''\intermed-ca'' in expression or statement."
Going back to the command you posted at the top of the OP, that is proper syntax, but won't be the Path you're looking for because the logic is flawed. So, what you posted as your command will not generate an error, but will return a path that likely doesn't exist. Assuming your working directory is your user profile, you will get something like this back for $CA_HOME: C:\Users\Zulgrib.Path\intermed-ca. The reason is that you're basically doing this:
$path = (Get-Location).Path
'{0}.Path\intermed-ca' -f $path
The .Path is part of the string, not the string execution. That's why you should add parenthesis to ensure that this property is returned as part of the string execution:
powershell.exe -NoExit -c Set-Variable -Name "CA_HOME" -Value "$((Get-Location).Path)\intermed-ca"
While this is fine and will work without issues, quote translations can be tricky from the command line. So, this really isn't a perfect solution for all scenarios. For better compatibility, I would get in the habit of wrapping the whole command in double quotes and keep single quotes inside the command:
powershell.exe -NoExit -c "Set-Variable -Name 'CA_HOME' -Value ('{0}\intermed-ca' -f (Get-Location).Path)"
For the best compatibility, consider base64 encoding your command and running it with -EncodedCommand. See the very bottom of powershell.exe /? for an example. Be weary that some situations will run into issues with overly long CLI commands.
Note: '{0}\intermed-ca' -f (Get-Location).Path and '{0}\intermed-ca' -f (Get-Location) will give the same result. When PowerShell knows it's injecting a PSOobject into a string, it'll give you the string form of what you likely want.

PowerShell Error - Missing expression after unary operator '-'

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.

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!

Powershell Reset Windows Update

I currently installed a fresh official Windows 7 Ultimate on my MacbookPro with BootCamp.
Everything is running good except Windows Update. Stuck on Searching for updates..
So I made a lot of google searches (for about 4 hours now) and I tried many things such as the FixIt tools from MS and other tricks but nothing is working.
Now i'm trying this:
http://www.sevenforums.com/performance-maintenance/372790-very-high-memory-sometimes-cpu-usage-svchost-exe-up-1-gb-post3095241.html#post3095241
I'm now at using PowerShell and entering this command line:
sc.exe sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)
I always get this error:
Missing closing ')' in expression.
At line:1 char:23
+ sc.exe sdset bits D:(A <<<< ;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRS
DRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)
+ CategoryInfo : ParserError: (CloseParenToken:TokenId) [], Paren
tContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInExpression
enter code here
I took a look at the commande line and I dont see any missing ')', and made some other google search for this command line from other websites and always give the same error
Whats wrong..!?
Update:
I tried this without success (same error)
$str = "sc.exe sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)"
Invoke-Expression $str
SOLUTION
I found the solution and its to type the command as follow:
CMD /C "sc.exe sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)"
When I put quotes around the entire command, the dos prompt removed the quote marks, and returned the result. Putting the quotes around the long parameter list, i.e., doing this:
sc.exe sdset bits "D:(A;; [snip] ;;PU)"
resulted in the Dos Prompt executing giving the message Success.
PowerShell does not play well with DOS executable files and their arguments.
Try using:
Start-Process sc.exe -ArgumentList 'sdset bits D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)'
Since this is invoking DOS use single quotes around the DOS command and add double quotes around the ACL "D:(A;;......;;PU)". It would look like this
Invoke-Expression -Command 'sc.exe sdset bits "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)"'

PowerShell mkdir alias + Set-StrictMode -Version 2. Strange bug. Why?

It's something unbelievable. This is a PowerShell code snippet in test.ps1 file:
Set-StrictMode -Version 2
mkdir c:\tmp\1 # same with 'md c:\tmp\1'
Start cmd.exe, navigate to folder with test.ps1 script and run it:
c:\tmp>powershell ".\test.ps1"
This produces the following error:
The variable '$_' cannot be retrieved because it has not been set.
At line:50 char:38
+ $steppablePipeline.Process($_ <<<< )
+ CategoryInfo : InvalidOperation: (_:Token) [], ParentContainsEr
rorRecordException
+ FullyQualifiedErrorId : VariableIsUndefined
Why?
It works when started from PowerShell console but not cmd.exe. I discovered this bug in much larger script. It was a WTF moment.
What is wrong with this simple script?
Even though a workaround has already been found, I thought people might be interested in an explanation.
As to why it behaves differently in the shell versus cmd.exe, see Powershell 2.0 - Running scripts for the command line call vs. from the ISE
As mentioned in the reference, there is a difference between the following two commands:
powershell ".\test.ps1"
powershell -File ".\test.ps1"
When using the first syntax, it seems to mess with scope, causing the Set-StrictMode command to modify the strict mode for functions defined at the global scope.
This triggers a bug (or an incorrect assumption, perhaps) in the definition of the mkdir function.
The function makes use of the GetSteppablePipeline method to proxy the pipeline for the New-Item cmdlet. However, the author neglected to account for the fact that the PROCESS section is still executed even when there is nothing in the pipeline. Thus, when the PROCESS section is reached, the $_ automatic variable is not defined. If strict mode is enabled, an exception will occur.
One way for Microsoft to account for this would be to replace following line:
$steppablePipeline.Process($_)
with the following:
if (test-path Variable:Local:_) {
$steppablePipeline.Process($_)
}
I admit that this may not be the best way to fix it, but the overhead would be negligible. Another option would be to somehow test if the pipeline is empty in the BEGIN section, and then set $_ to $null.
Either way, if you run your scripts with the "powershell.exe -File filename" syntax, then you won't need to worry about it.
It looks like a bug (in PowerShell).