Can't declare methods in a script being run in PowerShell - powershell

I'm invoking a script as described here and exemplified here. It's running and I can see the outputs to the console (both hazaa and shazoo). However, the method declared in it does not appear to be there, despite testing both versions below.
Invoke-Expression -Command $target
$target | Invoke-Expression
The contents of the file are like this.
Write-Host "Hazaa"
function TestPower { Write-Host "I got the power..." }
Write-Host "Shazoo"
When I execute the same function definition in the console, it's there, working fine. I'm not getting any errors on the manual execution nor the invokation from the other file. No warnings neither.
The weirdest part is that the function definitions done in the invoking script (i.d. the one that the invokes the execution not the one that in being targeted for the invokation).

To rephrase your issue - a function defined in a script invoked with Invoke-Expression isn't available after the expression has executed, i.e.:
test.ps1
Write-Host "Hazaa"
function TestPower { Write-Host "I got the power..." }
Write-Host "Shazoo"
Interactive
PS> Invoke-Expression -Command "C:\src\so\test.ps1"
PS> TestPower
TestPower : The term 'TestPower' 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
+ testpower
+ ~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (testpower:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
You can fix this by dot-sourcing the script in the expression instead:
PS> Invoke-Expression -Command ". 'C:\src\so\test.ps1'"
Hazaa
Shazoo
PS> TestPower
I got the power...
Note that the documentation for script scope and dot sourcing says:
Each script runs in its own scope. The functions, variables, aliases, and drives that are created in the script exist only in the script scope. You cannot access these items or their values in the scope in which the script runs.
Which explains why TestPower isn't available outside of your Invoke-Expression because the script is running in its own scope.
By contrast, using dot sourcing:
The dot sourcing feature lets you run a script in the current scope instead of in the script scope. ... After the script runs, you can use the created items and access their values in your session.

Related

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

Powershell spaces in script path

I am trying to streamline how I execute some scripts I wrote by setting up a function and alias to run them. I currently have functions to change my directory to where the scripts need to be run, but when I try to run the script itself I get the following error:
C:\Users\me\Desktop\BoB : The term 'C:\Users\me\Desktop\BoB' 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
+ C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Users\jteit\Desktop\BoB:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
The function is:
function run-scanner { "& 'C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1'" | Invoke-Expression }
I've tried a few variations based on other answers I've found, but I keep getting the same error. I would prefer to not remove the space on the path because other scripts use it successfully. Running the script from the ISE gives me no problems.
Ideally I would like the function to also allow me to have the script run on the folders I would like without changing the working directory (each script works on a particular set of files that are in a static location but some of them use $PWD to get the folders in the location).
For example in my $profile file I have this function: function go-to-temp {cd "C:\Users\me\Desktop\Bob Tools\To be Formatted\Temp"} which I run before I execute the above script. I would like them rolled into a single command without my working directory changing (which would render the go-to-temp function redundant.
What am I doing wrong?
There is no reason to run your script through Invoke-Expression.
Unless your script relies on $PWD, then you should be able to execute it with the call operator: &. As the other poster mentioned, you can use dot-sourcing (.) if you need the variables the script generates, but this will import all global objects (aliases, variables, functions) to your current scope. If it does rely on $PWD, you can utilize Start-Process with -WorkingDirectory to avoid changing where you're at.
function Start-Scanner {
& "$HOME\Desktop\BoB Tools\folderScannerV0.4.ps1"
}
or
function Start-Scanner {
$startArgs = #{
FilePath = "$PSHOME\powershell.exe"
ArgumentList = '-File', "`"$HOME\Desktop\BoB Tools\folderScannerV0.4.ps1`""
WorkingDirectory = "$HOME\Desktop\BoB Tools"
NoNewWindow = $true
Wait = $true
}
Start-Process #startArgs
}
You can just use dot-sourcing for this:
function run-scanner { . 'C:\Users\me\Desktop\BoB Tools\folderScannerV0.4.ps1' }

In VSTS, how can I reference a variable in the name of another variable?

I have a variable group that contains deployment credentials for an environment, test_host, test_token etc. I want to be able to use the values of these variables in a release task but I don't want to type in the environment name (test) as this can change between environments.
How can I reference the test_host variable using the current environment name? I've tried $($(Release.EnvironmentName)_host) which results in the string $(test_host) being passed to my script. How do I get it to resolve $(test_host)?
Note - I am trying to pass these values into a powerhsell script so in the arguments field I have -deployHost $($(Release.EnvironmentName)_host) which results in -deployHost $(test_host) being executed.
UPDATE 1
Whilst the values ultimately get passed into a powershell script, that script is within a task group. If I do it outside the task group (just a regular powershell task in the release) then it works. When I pass the value into a task group parameter, it fails. See logs below.
2017-08-24T12:58:50.5643742Z Generating script.
2017-08-24T12:58:50.7206133Z Formatted command: . 'C:\agent_work\r5\a\RCV\deploys\uxforms\Deploy-ToUxForms.ps1' -targetHost $(test_host)
2017-08-24T12:58:50.7206133Z ##[command]"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -File "C:\agent_work\_temp\baf9f7b8-2de2-4760-8cfd-67cd530801ed.ps1"
2017-08-24T12:58:50.7206133Z ##[error]test_host : The term 'test_host' 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 C:\agent_work\_temp\baf9f7b8-2de2-4760-8cfd-67cd530801ed.ps1:2 char:79
+ . 'C:\agent_work\r5\a\RCV\deploys\uxforms\Deploy-ToUxForms.ps1' -targetHost $(te ...
+ ~~
+ CategoryInfo : ObjectNotFound: (test_host:String) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : CommandNotFoundException
Thanks
Recreated all task groups and variables and this works now.

What does ". script" do?

What will the following lines of PowerShell code do?
Set-Location D:\Utilities\AZ
. D:\Utilities\AZ\Uti.ps1
I am sure about he first line that it changes the path but not really sure about the second.
The . C:\path\to\script.ps1 part is known as "Dot Sourcing".
When you run a script without the dot, the contents of the script is executed in its own scope (the Script scope), and so any variable or functions defined inside the script will not persist after the script is done executing.
When you dot source a script (like in your example), the contents of the script is executed in the the callers scope, and functions defined inside the script will persist even after it has run.
Quote from the about_Scripts help file:
SCRIPT SCOPE AND DOT SOURCING
Each script runs in its own scope. The functions, variables, aliases, and
drives that are created in the script exist only in the script scope. You
cannot access these items or their values in the scope in which the
script runs.
To run a script in a different scope, you can specify a scope, such as
Global or Local, or you can dot source the script.
The dot sourcing feature lets you run a script in the current scope instead
of in the script scope. When you run a script that is dot sourced, the
commands in the script run as though you had typed them at the command
prompt. The functions, variables, aliases, and drives that the script
creates are created in the scope in which you are working. After the script
runs, you can use the created items and access their values in your session.
To dot source a script, type a dot (.) and a space before the script path.
For example:
. C:\scripts\UtilityFunctions.ps1
-or-
. .\UtilityFunctions.ps1
Set-Location changes the working directory of the script to the given folder. The second statement dot-sources the PowerShell script, i.e. it loads the content of the file and runs it in the current context (see section SCRIPT SCOPE AND DOT SOURCING of the help topic I linked to).
Since you said you cannot run the script, you're most likely getting the following error when dot-sourcing it:
File D:\Utilities\AZ\Uti.ps1 cannot be loaded because the execution of scripts is
disabled on this system. Please see "get-help about_signing" for more details.
At line:1 char:2
+ . <<<< D:\Utilities\AZ\Uti.ps1
+ CategoryInfo : NotSpecified: (:) [], PSSecurityException
+ FullyQualifiedErrorId : RuntimeException
This means that the execution policy for PowerShell scripts on your system is set to Restricted. Use Get-ExecutionPolicy to verify that, and use Set-ExecutionPolicy to change it:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope User
or just
Set-ExecutionPolicy RemoteSigned
if you have administrative rights.
If Set-ExecutionPolicy fails, the execution policy is probably defined with a group policy, in which case you need to talk to your admins about it.
See here for a more detailed explanation of execution policy scopes.

Powershell | Unable to perform command with space character

Can anyone explain to me why:
iex "C:\Program Files\test\test.exe"
Returns:
C:\Program : The term 'C:\Program' 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
+ C:\Program Files\test\test.exe
+ ~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (C:\Program:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
I've tried to get this working multiple different ways:
Wrapping the text in ()
Putting the string into a variable and passing it as a variable instead
Using single quotes
Using double quotes
I don't know how else I can get it to realize that the entire string must be run, not just the first word.
Post-answered example
The question has been answered. Here is something I was trying to get working:
$tool = "C:\Windows\System32\cmd.exe"
$param = "/c ping google.com -n 1"
$test = & $tool $param
Write-Host $test
It turns out that the line with & does NOT work with double quotes "" in this instance, and actually worked without them. I think this has to do with there being arguments/parameters involved.
Use the & operator together with quotes:
& "C:\Program Files\test\test.exe"
From help about_operators:
& Call operator
Runs a command, script, or script block. The call operator, also known as
the "invocation operator," lets you run commands that are stored in
variables and represented by strings. Because the call operator does not
parse the command, it cannot interpret command parameters.
C:\PS> $c = "get-executionpolicy"
C:\PS> $c
get-executionpolicy
C:\PS> & $c
AllSigned