Script runs in ISE but not in Powershell - 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!

Related

Newbie powershell argument issues

I made powershell script that [1] accepts 2 arguments (aka parameters), [2] changes a file's modified date & time, and [3] writes something to host. The following command line works just fine in the powershell console, but triggers an error message when I run the same command line in a Windows cmd prompt (DOS) Window:
E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
The following is the coding for the powershell script to which I gave the long name, 'Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1':
param ( [string]$dateTimeVarArg, [string]$file_dateTimeMod_fullname)
Get-ChildItem $file_dateTimeMod_fullname | % {$_.LastWriteTime = $dateTimeVarArg}
#Get-ChildItem "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
$strString = "Hello World"
write-host $strString
function ftest{
$test = "Test"
write-host $test
}
ftest
When I run the command line shown above in a Windows DOS command prompt setting, I get the following error message:
Exception setting "LastWriteTime": "Cannot convert null to type "System.DateTime"."
At E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_1_named_arg_aaa.ps1:6 char:50
+ ... "E:\Apps\Delete01\test1.bat" | % {$_.LastWriteTime = $dateTimeVarArg}
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ExceptionWhenSetting
I would like to know [1] how to alter the command line shown above (which works fine in the powershell console) so that it works in a Windows DOS command prompt setting, and [2] where I can learn more about why my command line triggers errors, and how to avoid them.
According to the output from the command "Get-Host | Select-Object Version", I am running version. 5.1.19041.1682.
Any tips would be much appreciated.
By default, you can not directly execute PowerShell scripts (.ps1 files) from cmd.exe, the Windows legacy shell, or from outside PowerShell altogether.
Attempting to do so opens the script file for editing instead, as does double-clicking .ps1 files from File Explorer / the desktop.
Executing .ps1 scripts from outside PowerShell itself requires use of the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+), which in your case translates to a call such as (additional CLI parameters may be called for):
powershell.exe -File E:\Apps\UtilitiesByMarc\Change_DateTime_for_test1.bat_and_Hello_world_with_2_named_args_aaa.ps1 -dateTimeVarArg "01/11/2005 06:01:36" -file_dateTimeMod_fullname "E:\Apps\Delete01\test1.bat"
As for what you tried:
The fact that you were able to execute your .ps1 from cmd.exe suggests that you changed the file-type definition for such files to execute via powershell.exe instead.
The fact that the arguments you tried to pass to the script were ignored - as implied by the error message you got - suggests that you used File Explorer's Open with shortcut-menu command to choose to open all .ps1 files with powershell.exe; said method does not support argument-passing.
There is a way to change the file-type definition to support argument-passing too, and it is detailed in this answer (section "Programmatic method").
Generally, however, I suggest not applying this customization, especially in batch files that must also be run by other users / on other machines, which cannot be expected to have the same customization in place.

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.

Comment Based Help not displayed in Powershell

I am trying to implement Comment Based Help as described here with Powershell 5.1 on Windows Server 2016.
The script I am using is called blah.ps1:
<#
.DESCRIPTION
blah blah
#>
function Blahblah
{
}
In Powershell, I can load the script:
.\blah.ps1
But when I ask for help on this function using:
Get-Help Blahblah
Powershell only reports an error:
Get-Help : Get-Help could not find Blahblah in a help file in this session. To download updated help topics type: "Update-Help". To get help online, search for the help topic in the TechNet library at http://go.microsoft.com/fwlink/?LinkID=107116.
At line:1 char:1
+ Get-Help Blahblah
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (:) [Get-Help], HelpNotFoundException
+ FullyQualifiedErrorId : HelpNotFound,Microsoft.PowerShell.Commands.GetHelpCommand
I am assuming that Get-Help can be used with the custom help comments. What am I missing?
Import the script as a module instead of running it.
Import-Module .\blah.ps1
While declaring the function, including the help immediately after the function header, saving it in a module, and importing the module is probably the best way to handle it, you can make your script's help available to Get-Help by ensuring that...
the function name and the file name match (which means one function per file), and...
ensuring that the script file is in a directory that is in $env:PATH.
The help for the function can be declared before the function itself is, provided that there is no more than one blank line between the help's closing #> and the beginning of the function declaration.
See Get-Help about_Comment_Based_Help in either the PowerShell help or the linked Microsoft documentation.

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

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).