Executing a powershell script from another script with $PSScriptRoot? - powershell

I'm wondering what I'm missing here. I have a powershell script that calls another script with some parameters to execute as a way to keep things tidy. Here is what works:
C:\Scripts\Project\coolscript.ps1 -projname 'my.project' -domain 'work'
I want others to be able to use this script without having to change anything, so I thought I could make the path relative instead of the full one starting from C: so I thought I could execute the script like this:
$pathname = $PSScriptRoot + '\coolscript.ps1'
$pathname -projname 'my.project' -domain 'work'
however I get an error that says 'unexpected token in expression or statement for everything after $pathname
ANy ideas what I'm missing? Thank you

Use the Call operator (&) as follows:
& $pathname -projname 'my.project' -domain 'work'
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 or script blocks. The
call operator executes in a child scope. For more about scopes, see
about_scopes.

Related

Use Powershell console to run a script from a different folder

I have a Powershell script that takes two arguments.
param([string]$folder, [string]$filename)
When I open a Powershell shell, navigate to the folder that contains the script, and run it from there, the script is executed correctly:
C:\scriptfolder> ".\script.ps1" "C:\folder for argument" "filename.ext"
When I try to run the script from a different folder, I get an error message:
C:\otherfolder> "C:\scriptfolder\script.ps1" "C:\folder for argument" "filename.ext"
At line:1 char:nn
Unexpected token '"C:\folder for argument"' in expression or statement.
How can I run this script from another folder?
In order not to leave this question as 'Unanswered', here my comment as answer
There are two ways to do what you want. The first is by using dot-sourcing:
. "C:\scriptfolder\script.ps1" "C:\folder for argument" "filename.ext"
The second method would be by using the `& call operator
& "C:\scriptfolder\script.ps1" "C:\folder for argument" "filename.ext"
The difference between the two methods is that a script that is invoked using dot-sourcing runs in the current scope, while with the second method, using the call operator, scripts and functions run in a child scope.
You can find an excellent explanation in the answer here

Provide parameter to PS script stored as a string

This is the command that I provided to the customer before:
iex ((New-Object System.Net.WebClient).DownloadString('URL'))
It downloads PS script as a string from URL location and executes the script.
Now I need to pass parameter to this script
iex (((New-Object System.Net.WebClient).DownloadString('URL')) -Parameter 'parameter')
And it is not working because string not accepts parameters
I need to create the simple command (it is important) that will download the script from URL and accept parameters. Otherwise I would save this string to ps1 file and execute it passing the parameter
Could you please help me with that? I am new to PS
Try the following approach:
& ([scriptblock]::Create(
(New-Object System.Net.WebClient).DownloadString('URL')
)) 'Parameter'
Note:
The above runs the script text in a child scope, due to use of &, the call operator, as would happen with local invocation of a script file, whereas Invoke-Expression (iex) runs it in the current scope, i.e. as if it were called with . , the dot-sourcing operator. Change & to . if you want this behavior.
To download the script, you could alternatively use PowerShell's Invoke-RestMethod (irm) cmdlet: Invoke-RestMethod 'URL' or irm 'URL'
[scriptblock]::Create() creates a PowerShell script block, which can then be invoked with & (or .), while also accepting arguments.
As for what you tried:
Invoke-Expression (iex) - which should generally be avoided - has several drawbacks when it comes to executing a script file's content downloaded from the web:
It doesn't support passing arguments, as you've discovered.
An exit statement in the script text would cause the current PowerShell session to exit as a whole.
As noted, the code executes directly in the caller's scope, so that its variables, functions, ... linger in the session after execution (however, it's easy to avoid that with & { iex '...' }).
GitHub issue #5909 discusses enhancing the Invoke-Command cmdlet to robustly support download and execution of scripts directly from the web.

Powershell function call causes missing function error using powershell v7 on windows 10

I wrote a script to build all .net projects in a folder.
Issue
The issue is I am getting a missing function error when I call Build-Sollution.
What I tried
I made sure that function was declared before I used it so I am not really sure why it saids that it is not defined.
I am new to powershell but I would think a function calling another functions should work like this?
Thanks in advance!
Please see below for the error message and code.
Error Message
Line |
3 | Build-Sollution $_
| ~~~~~~~~~~~~~~~
The term 'Build-Sollution' 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.
Build-Sollution:
Code
param (
#[Parameter(Mandatory=$true)][string]$plugin_path,
[string]$depth = 5
)
$plugin_path = 'path/to/sollutions/'
function Get-Sollutions {
Get-ChildItem -File -Path $plugin_path -Include *.sln -Recurse
}
function Build-Sollution($solution) {
dotnet build $solution.fullname
}
function Build-Sollutions($solutions) {
$solutions | ForEach-Object -Parallel {
Build-Sollution $_
}
}
$solutions_temp = Get-Sollutions
Build-Sollutions $solutions_temp
From PowerShell ForEach-Object Parallel Feature | PowerShell
Script blocks run in a context called a PowerShell runspace. The runspace context contains all of the defined variables, functions and loaded modules.
...
And each runspace must load whatever module is needed and have any variable be explicitly passed in from the calling script.
So in this case, the easiest solution is to define Build-Sollution inside Build-Sollutions
As for this...
I am new to powershell but I would think a function calling another
functions should work like this?
... you cannot use the functions until you load your code into memory. You need to run the code before the functions are available.
If you are in the ISE or VSCode, if the script is not saved, Select All and hit use the key to run. In the ISE use F8 Selected, F5 run all. In VSCode, F8 run selected, crtl+F5 run all. YOu can just click the menu options as well.
If you are doing this from the consolehost, the run the script using dot sourcing.
. .\UncToYourScript.ps1
It's ok to be new, we all started somewhere, but it's vital that you get ramped up first. so, beyond what I address here, be sure to spend time on Youtube and search for Beginning, Intermediate, Advanced PowerShell for videos to consume. There are tons of free training resources all over the web and using the built-in help files would have given you the answer as well.
about_Scripts
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.
See also:
'powershell .net projects build run scripts'
'powershell build all .net projects in a folder'
Simple build script using Power Shell
Update
As per your comments below:
Sure the script should be saved, using whatever editor you choose.
The ISE does not use PSv7 by design, it uses WPSv5x and earlier.
The editor for PSv7 is VSCode. If you run a function that contains another function, you have explicitly loaded everything in that call, and as such it's available.
However, you are saying, you are using PSv7, so, you need to run your code in the PSv7 consolehost or VSCode, not the ISE.
Windows PowerShell (powershell.exe and powershell_ise.exe) and PowerShell Core (pwsh.exe) are two different environments, with two different executables, designed to run side-by-side on Windows, but you do have to explicitly choose which to use or write your code to branch to a code segment to execute relative to the host you started.
For example, let's say I wanted to run a console command and I am in the ISE, but I need to run that in Pwsh. I use a function like this that I have in a custom module autoloaded via my PowerShell profiles:
# Call code by console executable
Function Start-ConsoleCommand
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('scc')]
Param
(
[string]$ConsoleCommand,
[switch]$PoSHCore
)
If ($PoSHCore)
{Start-Process pwsh -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
Else {Start-Process powershell -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
}
All this code is doing is taking whatever command I send it and if I use the PoSHCore switch...
scc -ConsoleCommand 'SomeCommand' -PoSHCore
... it will shell out to PSCore, run the code, otherwise, it just runs from the ISE>
If you want to use the ISE with PSv7 adn not do the shell out thing, you need to force the ISE to use PSv7 to run code. See:
Using PowerShell Core 6 and 7 in the Windows PowerShell ISE

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

How to give the path as a parameter or a variable in powershell

I have written a powershell script. the code has paths related to only my PC.
Now the same code cannot be executed by another person on his machine because the path is diff. Therefore please let me know a way where my code can work on all machines.
It depends on the paths. If they're to programs in \Program Files perhaps you can use the environment variable $env:ProgramFiles in your path spec. You can also parameterize your script to take the path like so:
param($path)
# rest of script ...
Note that the param() statement must be the first non-comment line in your script.
You could also use the special $MyInvocation variable available to running scripts. It has access to the path the script was executed from, among other things.
For example a script I use has this line:
$InputCSV = (split-path $myinvocation.mycommand.path) + "\filename.csv"
Which means no matter where the script is run from it will know to grab the CSV file from the same place.