How can I get the current PowerShell executing file? - powershell

Note: PowerShell 1.0
I'd like to get the current executing PowerShell file name. That is, if I start my session like this:
powershell.exe .\myfile.ps1
I'd like to get the string ".\myfile.ps1" (or something like that). EDIT: "myfile.ps1" is preferable.
Any ideas?

I've tried to summarize the various answers here, updated for PowerShell 5:
If you're only using PowerShell 3 or higher, use $PSCommandPath
If want compatibility with older versions, insert the shim:
if ($PSCommandPath -eq $null) { function GetPSCommandPath() { return $MyInvocation.PSCommandPath; } $PSCommandPath = GetPSCommandPath }
This adds $PSCommandPath if it doesn't already exist.
The shim code can be executed anywhere (top-level or inside a function), though $PSCommandPath variable is subject to normal scoping rules (eg, if you put the shim in a function, the variable is scoped to that function only).
Details
There's 4 different methods used in various answers, so I wrote this script to demonstrate each (plus $PSCommandPath):
function PSCommandPath() { return $PSCommandPath }
function ScriptName() { return $MyInvocation.ScriptName }
function MyCommandName() { return $MyInvocation.MyCommand.Name }
function MyCommandDefinition() {
# Begin of MyCommandDefinition()
# Note: ouput of this script shows the contents of this function, not the execution result
return $MyInvocation.MyCommand.Definition
# End of MyCommandDefinition()
}
function MyInvocationPSCommandPath() { return $MyInvocation.PSCommandPath }
Write-Host ""
Write-Host "PSVersion: $($PSVersionTable.PSVersion)"
Write-Host ""
Write-Host "`$PSCommandPath:"
Write-Host " * Direct: $PSCommandPath"
Write-Host " * Function: $(PSCommandPath)"
Write-Host ""
Write-Host "`$MyInvocation.ScriptName:"
Write-Host " * Direct: $($MyInvocation.ScriptName)"
Write-Host " * Function: $(ScriptName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Name:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Name)"
Write-Host " * Function: $(MyCommandName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Definition:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Definition)"
Write-Host " * Function: $(MyCommandDefinition)"
Write-Host ""
Write-Host "`$MyInvocation.PSCommandPath:"
Write-Host " * Direct: $($MyInvocation.PSCommandPath)"
Write-Host " * Function: $(MyInvocationPSCommandPath)"
Write-Host ""
Output:
PS C:\> .\Test\test.ps1
PSVersion: 5.1.19035.1
$PSCommandPath:
* Direct: C:\Test\test.ps1
* Function: C:\Test\test.ps1
$MyInvocation.ScriptName:
* Direct:
* Function: C:\Test\test.ps1
$MyInvocation.MyCommand.Name:
* Direct: test.ps1
* Function: MyCommandName
$MyInvocation.MyCommand.Definition:
* Direct: C:\Test\test.ps1
* Function:
# Begin of MyCommandDefinition()
# Note this is the contents of the MyCommandDefinition() function, not the execution results
return $MyInvocation.MyCommand.Definition;
# End of MyCommandDefinition()
$MyInvocation.PSCommandPath:
* Direct:
* Function: C:\Test\test.ps1
Notes:
Executed from C:\, but actual script is C:\Test\test.ps1.
No method tells you the passed invocation path (.\Test\test.ps1)
$PSCommandPath is the only reliable way, but was introduced in PowerShell 3
For versions prior to 3, no single method works both inside and outside of a function

While the current Answer is right in most cases, there are certain situations that it will not give you the correct answer. If you use inside your script functions then:
$MyInvocation.MyCommand.Name
Returns the name of the function instead name of the name of the script.
function test {
$MyInvocation.MyCommand.Name
}
Will give you "test" no matter how your script is named.
The right command for getting the script name is always
$MyInvocation.ScriptName
this returns the full path of the script you are executing. If you need just the script filename than this code should help you:
split-path $MyInvocation.PSCommandPath -Leaf

If you only want the filename (not the full path) use this:
$ScriptName = $MyInvocation.MyCommand.Name

Try the following
$path = $MyInvocation.MyCommand.Definition
This may not give you the actual path typed in but it will give you a valid path to the file.

beware:
Unlike the $PSScriptRoot and $PSCommandPath automatic variables, the
PSScriptRoot and PSCommandPath properties of the $MyInvocation automatic
variable contain information about the invoker or calling script, not the
current script.
e.g.
PS C:\Users\S_ms\OneDrive\Documents> C:\Users\SP_ms\OneDrive\Documents\DPM ...
=!C:\Users\S_ms\OneDrive\Documents\DPM.ps1
...where DPM.ps1 contains
Write-Host ("="+($MyInvocation.PSCommandPath)+"!"+$PSCommandPath)

If you are looking for the current directory in which the script is being executed, you can try this one:
$fullPathIncFileName = $MyInvocation.MyCommand.Definition
$currentScriptName = $MyInvocation.MyCommand.Name
$currentExecutingPath = $fullPathIncFileName.Replace($currentScriptName, "")
Write-Host $currentExecutingPath

As noted in previous responses, using "$MyInvocation" is subject to scoping issues and doesn't necessarily provide consistent data (return value vs. direct access value). I've found that the "cleanest" (most consistent) method for getting script info like script path, name, parms, command line, etc. regardless of scope (in main or subsequent/nested function calls) is to use "Get-Variable" on "MyInvocation"...
# Get the MyInvocation variable at script level
# Can be done anywhere within a script
$ScriptInvocation = (Get-Variable MyInvocation -Scope Script).Value
# Get the full path to the script
$ScriptPath = $ScriptInvocation.MyCommand.Path
# Get the directory of the script
$ScriptDirectory = Split-Path $ScriptPath
# Get the script name
# Yes, could get via Split-Path, but this is "simpler" since this is the default return value
$ScriptName = $ScriptInvocation.MyCommand.Name
# Get the invocation path (relative to $PWD)
# #GregMac, this addresses your second point
$InvocationPath = ScriptInvocation.InvocationName
So, you can get the same info as $PSCommandPath, but a whole lot more in the deal. Not sure, but it looks like "Get-Variable" was not available until PS3 so not a lot of help for really old (not updated) systems.
There are also some interesting aspects when using "-Scope" as you can backtrack to get the names, etc. of the calling function(s). 0=current, 1=parent, etc.
Hope this is somewhat helpful.
Ref, https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-variable

I would argue that there is a better method, by setting the scope of the variable $MyInvocation.MyCommand.Path:
ex> $script:MyInvocation.MyCommand.Name
This method works in all circumstances of invocation:
EX:
Somescript.ps1
function printme () {
"In function:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
}
"Main:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
" "
printme
exit
OUTPUT:
PS> powershell C:\temp\test.ps1
Main:
MyInvocation.ScriptName:
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: test.ps1
In function:
MyInvocation.ScriptName: C:\temp\test.ps1
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: printme
Notice how the above accepted answer does NOT return a value when called from Main. Also, note that the above accepted answer returns the full path when the question requested the script name only. The scoped variable works in all places.
Also, if you did want the full path, then you would just call:
$script:MyInvocation.MyCommand.Path

A short demonstration of #gregmac's (excellent and detailed) answer, which essentially recommends $PSCommandPath as the only reliable command to return the currently running script where Powershell 3.0 and above is used.
Here I show returning either the full path or just the file name.
Test.ps1:
'Direct:'
$PSCommandPath # Full Path
Split-Path -Path $PSCommandPath -Leaf # File Name only
function main () {
''
'Within a function:'
$PSCommandPath
Split-Path -Path $PSCommandPath -Leaf
}
main
Output:
PS> .\Test.ps1
Direct:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Within a function:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1

Did some testing with the following script, on both PS 2 and PS 4 and had the same result. I hope this helps people.
$PSVersionTable.PSVersion
function PSscript {
$PSscript = Get-Item $MyInvocation.ScriptName
Return $PSscript
}
""
$PSscript = PSscript
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
""
$PSscript = Get-Item $MyInvocation.InvocationName
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
Results -
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts

This can works on most powershell versions:
(& { $MyInvocation.ScriptName; })
This can work for Scheduled Job
Get-ScheduledJob |? Name -Match 'JOBNAMETAG' |% Command

Related

Getting the first argument passed to the cli

Trying just to write a simple script that would return the SHA256 signature of a file using the file name passed to my ps1 script :
The scriptname is sha256sum.ps1.
The first argument will be any file, example :
sha256sum.ps1 dummy.exe
I tried these inside sha256sum.ps1 :
Get-FileHash -algo SHA256 %1
Get-FileHash -algo SHA256 $1
Get-FileHash -algo SHA256 $args[1]
but none of them worked.
Is there a simple way to do that ?
EDIT : Here is the final version of my script thanks to your help, guys :)
#!/usr/bin/env pwsh
param( $firstArg )
function calcSignature( $filename ) {
$scriptName = Split-Path -Leaf $PSCommandPath
switch( $scriptName ) {
"md5sum.ps1" { $algo = "MD5"; Break }
"sha1sum.ps1" { $algo = "SHA1"; Break }
"sha256sum.ps1" { $algo = "SHA256"; Break }
"sha384sum.ps1" { $algo = "SHA384"; Break }
"sha512sum.ps1" { $algo = "SHA512"; Break }
}
(Get-FileHash -algo $algo $filename).Hash + " " + $filename
}
calcSignature( $firstArg )
Now I only have one script and the others are links pointing to sha256sum.ps1.
I'm guessing you're looking for "How to pass an argument to your .ps1 script".
This is an example of how the script sha256sum.ps1 would look:
param(
[parameter(mandatory)]
[validatescript({
Test-Path $_ -PathType Leaf
})]
[system.io.fileinfo]$File
)
(Get-FileHash $File -Algorithm SHA256).Hash
Now, if we were to call this script, as an example:
PS \> .\sha256sum.ps1 .\test.html
1B24ED8C7929739D296DE3C5D6695CA40D8324FBF2F0E981CF03A8A6ED778C9C
Note: the current directory is where the script and the html file are located, if that was not the case, you should use the absolute path.
I would recommend you to the official docs to get a concept on functions and the param(...) block.
Santiago's helpful answer shows how you to properly declare parameters for your script, which is generally preferable.
As for what you tried:
The automatic $args variable contains arguments not bound to any declared parameters. In other words: if your script doesn't declare any parameters, $args is the only way to access any arguments that were passed.
The first argument is at index 0, not 1.
Note:
This differs from other shells / programming language where the element at index 0 reflects the script / program being called.
To get the full path of the enclosing script in PowerShell, use the automatic $PSCommandPath variable.
Thus, for instance, %1 in a batch file and $1 in a bash script - both of which contain the first argument - correspond to $args[0] in PowerShell.
Therefore, Get-FileHash -algo SHA256 $args[0] should have worked.

Run PowerShell script from another script, passing string variables

I got two Powershell script.
The first, script1.ps1, got some Global string variable.
The second, script2.ps1, must do some operation with values of the string variables, then close without passing any data to the first.
Here pieces of code.
script1.ps1
#[ENG] Global $language contains the language name: EN-US for american english, EN-GB for english of Great Britain, for example.
$global:language = $culture.Name
#[ENG] Global $workPath variable contains the path of the folder with the original files.
$global:workPath = "D:\Documents\Downloads\Emule-Incoming\osis"
#[ENG] Global $scriptPath variable contains the path of the script(s) files.
$local:temp = Split-Path $PSCommandPath
$global:scriptPath = "$temp"
$temp = ""
##########
# mods.d #
##########
#[ENG] Global $modBip variable contains the path of destination of CONF files for BPBiblePortable.
$global:modBip = "D:\Documents\Downloads\Emule-Incoming\BPBiblePortable\App\BPBible\resources\mods.d\"
#[ENG] Global $modXip contains the path of destination of CONF files for xiphos.
$global:modXip = "C:\Users\Emanuele\AppData\Roaming\Sword\mods.d\"
#[ENG] Global $modGit variable contains the path of destination of CONF files in GitHub Desktop to upload and synchronize in GitHub.
$global:modGit = "D:\Documents\GitHub\EmanueleTinari\mods.d\"
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 $language $workPath $scriptPath $modBiP $modXip $modGit'
Invoke-Expression & $scriptPath
script2.ps1
#[ENG] Retrieve Global variable's values.
Param ([string]$language, [string]$workPath, [string]$scriptPath, [string]$modBiP, [string]$modXip, [string]$modGit)
Write-Host $language
Write-Host $workPath
Write-Host $scriptPath
Write-Host $modBiP
Write-Host $modXip
Write-Host $modGit
What I try:
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 -$language $language -$workPath $workPath -$scriptPath $scriptPath -$modBiP $modBiP -$modXip $modXip -$modGit $modGit'
Invoke-Expression & $scriptPath
I add -$[string name] : nothing to do.
$scriptPath = $scriptPath + "\" + 'cei1974.ps1 -$language $language -$workPath $workPath -$scriptPath $scriptPath -$modBiP $modBiP -$modXip $modXip -$modGit $modGit'
Invoke-Expression $scriptPath
Without & near $scriptPath : nothing to do.
I, first write this question, examined and tryed answers in this posts:
Run a PowerShell script from another one
PowerShell Script call out from another PowerShell Script
Invoke powershell script from another
Passing varibale into powershell script that is being executed from another script
How can I achieve to pass strings between this two script? Tnx in advance.
Swap-out the last 2 lines in your script1.ps1 script with this:
. $scriptPath\script2.ps1 -language $language -workPath $workPath -scriptPath $scriptPath -modBiP $modBiP -modXip $modXip -modGit $modGit
Some notes:
Do not use Invoke-Expression followed by the ampersand (&) call operator, as that is unnecessary.
You have all the information necessary to call script2.ps1 directly, rather than build-up a string and rely on expression evaluation.
When specifying script command-line parameters by name, simply use a hyphen (-) followed by the parameter name. For example, -language $language.
I believe you want to use dot-sourcing (.) to call your script2.ps1. You could also defer to using the ampersand (&) call operator. Invoke-Command and Invoke-Expression could be used, but can also be exploited if not handled correctly.
For more information, see Get-Help about_Operators and Get-Help about_Scripts and Get-Help about_Parameters

Can a function know from where it is exectuted?

$MyInvocation can be used to identify where the source of a script is located.
How can a function do the same?
function whereami {
Write-Host $MyInvocation.MyCommand.Path # produces nothing
}
Write-Host $MyInvocation.MyCommand.Path
whereami
The C programmer would use __FILE__.
There is $PSCommandPath which returns the full path to the script being executed.
Note that the script must be saved as a file before this will work in the ISE.
function whereami {
$PSCommandPath
}
Write-Host $MyInvocation.MyCommand.Path
whereami
Returns:
PS C:\Users\> C:\Users\somescript.ps1
C:\Users\somescript.ps1
C:\Users\somescript.ps1

How to return the name of the calling script from a Powershell Module?

I have two Powershell files, a module and a script that calls the module.
Module: test.psm1
Function Get-Info {
$MyInvocation.MyCommand.Name
}
Script: myTest.ps1
Import-Module C:\Users\moomin\Documents\test.psm1 -force
Get-Info
When I run ./myTest.ps1 I get
Get-Info
I want to return the name of the calling script (test.ps1). How can I do that?
Use PSCommandPath instead in your module:
Example test.psm1
function Get-Info{
$MyInvocation.PSCommandPath
}
Example myTest.ps1
Import-Module C:\Users\moomin\Documents\test.psm1 -force
Get-Info
Output:
C:\Users\moomin\Documents\myTest.ps1
If you want only the name of the script that could be managed by doing
GCI $MyInvocation.PSCommandPath | Select -Expand Name
That would output:
myTest.ps1
I believe you could use the Get-PSCallStack cmdlet, which returns an array of stack frame objects. You can use this to identify the calling script down to the line of code.
Module: test.psm1
Function Get-Info {
$callstack = Get-PSCallStack
$callstack[1].Location
}
Output:
myTest.ps1: Line 2
Using the $MyInvocation.MyCommand is relative to it's scope.
A simple example (Of a script located : C:\Dev\Test-Script.ps1):
$name = $MyInvocation.MyCommand.Name;
$path = $MyInvocation.MyCommand.Path;
function Get-Invocation(){
$path = $MyInvocation.MyCommand.Path;
$cmd = $MyInvocation.MyCommand.Name;
write-host "Command : $cmd - Path : $path";
}
write-host "Command : $cmd - Path : $path";
Get-Invocation;
The output when running .\c:\Dev\Test-Script.ps1 :
Command : C:\Dev\Test-Script.ps1 - Path : C:\Dev\Test-Script.ps1
Command : Get-Invocation - Path :
As you see, the $MyInvocation is relative to the scoping. If you want the path of your script, do not enclose it in a function. If you want the invocation of the command, then you wrap it.
You could also use the callstack as suggested, but be aware of scoping rules.
I used this today after trying a couple of techniques.
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$ScriptName = $MyInvocation.MyCommand | select -ExpandProperty Name
Invoke-Expression ". $Script\$ScriptName"
To refer to the invocation info of the calling script, use:
#(Get-PSCallStack)[1].InvocationInfo
e.g.:
#(Get-PSCallStack)[1].InvocationInfo.MyCommand.Name
This provides the script path with trailing backslash as one variable and the script name as another.
The path works with Powershell 2.0 and 3.0 and 4.0 and probably 5.0
Where with Posershell $PSscriptroot is now available.
$_INST = $myinvocation.mycommand.path.substring(0,($myinvocation.mycommand.path.length - $MyInvocation.mycommand.name.length))
$_ScriptName = $myinvocation.mycommand.path.substring($MyInvocation.MyCommand.Definition.LastIndexOf('\'),($MyInvocation.mycommand.name.length +1))
$_ScriptName = $_ScriptName.TrimStart('\')
If you want a more reusable approach, you can use:
function Get-CallingFileName
{
$cStack = #(Get-PSCallStack)
$cStack[$cStack.Length-1].InvocationInfo.MyCommand.Name
}
The challenge I had was having a function that could be reused within the module. Everything else assumed that the script was calling the module function directly and if it was removed even 1 step, then the result would be the module file name. If, however, the source script is calling a function in the module which is, in turn, calling another function in the module, then this is the only answer I've seen that can ensure you're getting the source script info.
Of course, this approach is based on what #iRon and #James posted.
For you googlers looking for quick copy paste solution,
here is what works in Powershell 5.1
Inside your module:
$Script = (Get-PSCallStack)[2].Command
This will output just the script name (ScriptName.ps1) which invoked a function located in module.
I use this in my module:
function Get-ScriptPath {
[CmdletBinding()]
param (
[string]
$Extension = '.ps1'
)
# Allow module to inherit '-Verbose' flag.
if (($PSCmdlet) -and (-not $PSBoundParameters.ContainsKey('Verbose'))) {
$VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
}
# Allow module to inherit '-Debug' flag.
if (($PSCmdlet) -and (-not $PSBoundParameters.ContainsKey('Debug'))) {
$DebugPreference = $PSCmdlet.GetVariableValue('DebugPreference')
}
$callstack = Get-PSCallStack
$i = 0
$max = 100
while ($true) {
if (!$callstack[$i]) {
Write-Verbose "Cannot detect callstack frame '$i' in 'Get-ScriptPath'."
return $null
}
$path = $callstack[$i].ScriptName
if ($path) {
Write-Verbose "Callstack frame '$i': '$path'."
$ext = [IO.Path]::GetExtension($path)
if (($ext) -and $ext -eq $Extension) {
return $path
}
}
$i++
if ($i -gt $max) {
Write-Verbose "Exceeded the maximum of '$max' callstack frames in 'Get-ScriptPath'."
return $null
}
}
return $null
}
You can grab the automatic variable MyInvocation from the parent scope and get the name from there.
Get-Variable -Scope:1 -Name:MyInvocation -ValueOnly
I did a basic test to check to see if it would always just get the direct parent scope and it worked like a treat and is extremely fast as opposed to Get-PSCallStack
function ScopeTest () {
Write-Information -Message:'ScopeTest'
}
Write-nLog -Message:'nLog' -Type:110 -SetLevel:Verbose
ScopeTest

Can I resolve PowerShell scriptblock parameters without invoking?

I'm looking at writing some PowerShell code that can either execute immediately, or produce the commands it would execute as generated scripts.
I'd like to avoid this scenario:
if($Generating){
write-Output "somecommand.exe"
}
else{
somecommand.exe
}
I got looking at ScriptBlocks, which at first looked promising because I can write the contents of the ScriptBlock to the console without executing it. Such as:
$sc = { somecommand.exe }
$sc
somecommand.exe
My specific question is, if my scriptblock contains parameters, can I get them to resolve when I'm writing the scriptblock contents to the console, but WITHOUT invoking the scriptblock?
For example given the following scriptblock:
$b2 = { Param([string]$P) Write-Host "$P" }
When I just type "$b2" at the console and hit enter I see this:
Param([string]$P) Write-Host "$P"
What I'd like to see is this (if the parameter value is "Foo"):
Param([string]$P) Write-Host "Foo"
I realize this can be done when it's invoked, either via "&" or using Invoke(), but would there be any way to get the parameters to resolve without invoking to make my script generation a little more elegant without needing a bunch of conditional statements throughout the code?
In PowerShell v3, you can get the param info via the AST property e.g.:
PS> $sb = {param($a,$b) "a is $a b is $b"}
PS> $sb.Ast.ParamBlock
Attributes Parameters Extent Parent
---------- ---------- ------ ------
{} {$a, $b} param($a,$b) {param($a,$b) "a...
Solution suitable for PowerShell v2:
# given the script block
$b2 = { Param([string]$P) Write-Host "$P" }
# make a function of it and "install" in the current scope
Invoke-Expression "function tmp {$b2}"
# get the function and its parameters
(Get-Command tmp).Parameters
When displaying a here-string with double quotes #" , it expands the variables. For the variables that should'nt expand, escape the variable with a backtick ( ` ).
So try this:
$P = "Foo"
$b2 = #"
{ Param([string]`$P) Write-Host "$P" }
"#
Test:
PS-ADMIN > $b2
{ Param([string]$P) Write-Host "Foo" }
If you want to convert it to scriptblock-type again:
#Convert it into scriptblock
$b3 = [Scriptblock]::Create($b2)
PS-ADMIN > $b3
{ Param([string]$P) Write-Host "Foo" }
PS-ADMIN > $b3.GetType().name
ScriptBlock
Using some of the suggestions I think I've found the best solution for my needs. Consider the following code
function TestFunc
{
Param(
[Parameter(Mandatory=$true)]
[string]$Folder,
[Parameter(Mandatory=$true)]
[string]$Foo
)
$code = #"
Write-Host "This is a folder $Folder"
Write-Host "This is the value of Foo $Foo"
"#
$block = [Scriptblock]::Create($code)
Write-Host "Running the block" -BackgroundColor Green -ForegroundColor Black
&$block
Write-Host "Displaying block code" -BackgroundColor Green -ForegroundColor Black
$block
}
And it's output:
Running the block
This is a folder c:\some\folder
This is the value of Foo FOOFOO
Displaying block code
Write-Host "This is a folder c:\some\folder"
Write-Host "This is the value of Foo FOOFOO"
By doing it this way, I still get all the benefit of keeping my existing functions and their parameters, parameter validation, CBH etc. I can also easily generate the code that the function would execute or just let it execute. Thanks for all the input, it's definitely been a good learning experience.
If you want to express your block as a block, not a string, the following works:
$printable = invoke-expression ('"' + ($block -replace '"', '`"') + '"')
Essentially, you're wrapping everything in quotes and then invoking it as an expression. The -replace call ensures any quotes in the block itself are escaped.
I'm using this in this handy function, which also halts execution if the invoked command failed.
# usage: exec { dir $myDir }
function exec($block)
{
# expand variables in block so it's easier to see what we're doing
$printable = invoke-expression ('"' + ($block -replace '"', '`"').Trim() + '"')
write-host "# $printable" -foregroundcolor gray
& $block
if ($lastExitCode -ne 0)
{
throw "Command failed: $printable in $(pwd) returned $lastExitCode"
}
}