Powershell 2.0 - extract parameters from script block - powershell

Is there a way to extract the parameter list of a script block from outside the script block in PS 2.0 ?
Say we have
$scriptb = { PARAM($test) }
In Powershell 3.0 we can do this
$scriptb.Ast.ParamBlock.Parameters.Count == 1 #true
The Ast property however was included in powershel 3.0 so the above will not work in PS 2.0 https://msdn.microsoft.com/en-us/library/System.Management.Automation.ScriptBlock_properties%28v=vs.85%29.aspx
Do you know of a way to do this in PS 2.0 ?

Perhaps it is not a pretty solution but it gets the job done:
# some script block
$sb = {
param($x, $y)
}
# make a function with the scriptblock
$function:GetParameters = $sb
# get parameters of the function
(Get-Command GetParameters -Type Function).Parameters
Output:
Key Value
--- -----
x System.Management.Automation.ParameterMetadata
y System.Management.Automation.ParameterMetadata

What about this?
$Scriptb = {
PARAM($test,$new)
return $PSBoundParameters
}
&$Scriptb "hello" "Hello2"

It looks like I can do this
function Extract {
PARAM([ScriptBlock] $sb)
$sbContent = $sb.ToString()
Invoke-Expression "function ____ { $sbContent }"
$method = dir Function:\____
return $method.Parameters
}
$script = { PARAM($test1, $test2, $test3) }
$scriptParameters = Extract $script
Write-Host $scriptParameters['test1'].GetType()
It will return a list of System.Management.Automation.ParameterMetadata
https://msdn.microsoft.com/en-us/library/system.management.automation.parametermetadata_members%28v=vs.85%29.aspx
I think there should be a better way. Until then I will use a variation of the above code.

Related

How to test that a powershell function has a cmdletbinding attribute

I'm trying out writing test-driven PowerShell code using Pester. Is there a way to test if a function has a certain CmdletBinding attribute, e.g. SupportsShouldProcess and ConfirmImpact:
function Remove-Something {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param ()
...
Is that kind of metadata available from the result of e.g. Get-Command?
$functionUnderTest = Get-Command Remove-Something
# How to test if $functionUnderTest has SupportsShouldProcesses defined?
It's a bit of a mouthful, but you can try something like this to get the attributes:
function Invoke-MyFunction {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
param ()
}
(get-command Invoke-MyFunction).ScriptBlock.Ast.Body.ParamBlock.Attributes
#PositionalArguments : {}
#NamedArguments : {SupportsShouldProcess, ConfirmImpact}
#TypeName : CmdletBinding
#Extent : [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
#Parent : param ()
and then you'll need to filter that to find an attribute called "CmdletBinding" and check the arguments specified on it.
$command = Get-Command -Name "Invoke-MyFunction";
$attributes = $command.ScriptBlock.Ast.Body.ParamBlock.Attributes;
$cmdletBinding = $attributes | where-object { $_.TypeName.FullName -eq "CmdletBinding" };
$supportsShouldProcess = $cmdletBinding.NamedArguments | where-object { $_.ArgumentName -eq "SupportsShouldProcess" };
$confirmImpact = $cmdletBinding.NamedArguments | where-object { $_.ArgumentName -eq "ConfirmImpact" };
From there you can check whatever other parts of the definition you want to confirm exist...
Note - error handling left as an exercise for the reader :-)
In case someone else is interested in Test-driven development with PowerShell using Pester. Based on mclayton's answer above, I have created a set of test functions and published them in a PowerShell module called TDDUtils.
It has functions like Test-TDDCmdletBinding and Test-TDDOutputType that allows you to write Pester tests like:
function My-Command
{
[CmdletBinding()]
...
}
It "Should be an advanced function" {
$c = Get-Command -Name My-Command
Test-TDDCmdletBinding $c | Should -BeTrue
}
And
function My-Command
{
[OutputType([Bool])]
...
}
It "Should have Output type Bool" {
$c = Get-Command -Name My-Command
Test-TDDOutputType $c -TypeName 'Bool' | Should -BeTrue
}
Install it from a PowerShell administrator shell:
Install-Module TDDUtils
https://github.com/hansEricG/TDDUtils for complete source and more information.

How to run EXE in powershell with parameters, in one line

I need to run a .exe with parameters, currently this takes two lines. I would like to be able to pass a string as a variable to a function, and only need to do this once.
This is how I am currently doing it, which takes two variables:
function Foo{
$ExeToStart = "C:\Program Files\x\program.exe"
Start-Process $ExeToStart -ArgumentList "--arg1","--arg2"
}
Is there a way of combining this so that I can just define a variable, "y" and pass it into the function as one line similar to below?
$x = "C:\Program Files\x\program.exe -ArgumentList "--arg1","--arg2""
function Foo{
Start-Process $x
}
Here is a way you can achieve what you want.
# Create an array of the program and arguments
$x = 'C:\Program Files\x\program.exe','--arg1','--arg2'
# Example with no parameters
Function Foo {
Start-Process -FilePath $args[0][0] -ArgumentList $args[0][1..$args[0].Count]
}
# Example using parmeters
Function Foo {
param([string[]]$program)
$params = #{
FilePath = $program[0]
}
if ($program.Count -gt 1) {
$params['ArgumentList'] = $program[1..$program.Count]
}
Start-Process #Params
}
# Call the function
Foo $x

Powershell get return value from visual basic script

I have some old code in visual basic script. Instead of re-writing this old code into PowerShell, I'd like to call the VB scripts from PowerShell and capture their return value.
How can I get the return value of a visual basic script in powershell?
Something like this:
$returnValue = Invoke-Command -ScriptBlock{.\vbs\legacyVbsFunction.vbs}
The visual basic function may look like this
Function MyFunction() As Double
Return 3.87 * 2
End Function
It sounds like you want to capture a VBScript's (stdout) output:
$output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
Note that $output will either be a single string - if the script outputs just 1 line - or an array of strings in case of multi-line output.
For example, assuming that .\vbs\legacyVbsFunction.vbs contains the following code:
Function MyFunction
MyFunction = 3.87 * 2
End Function
' Call the function and print it to stdout.
Wscript.Echo(MyFunction)
You could capture the output and convert it to a [double] as follows:
[double] $output = cscript.exe //nologo .\vbs\legacyVbsFunction.vbs
$output then contains 7.74.
You can actually embed a vbscript function right in powershell using a com object called ScriptControl, but it only works in 32-bit powershell, C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe: Embed VBS into PowerShell
function Call-VBScript
{
$sc = New-Object -ComObject ScriptControl
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = Call-VBScript
$returnvalue = $vb.MyFunction()
"returnvalue is " + $returnvalue
I found out you can run a job as 32-bit:
$returnvalue =
start-job {
function Call-VBScript {
$sc = New-Object -ComObject MSScriptControl.ScriptControl.1
$sc.Language = 'VBScript'
$sc.AddCode('
Function MyFunction
MyFunction = 3.87 * 2
End Function
')
$sc.CodeObject
}
$vb = call-vbscript
$vb.MyFunction()
} -runas32 | wait-job | receive-job
"returnvalue is " + $returnvalue
You don't really have an exit code in the VBS, you have a return for a function.
To actually have a return code you must close the script with something like:
wscript.quit(0)
or
wscript.quit(returncode)
In the powershell script you must execute the vbs something like this:
(Start-Process -FilePath "wscript.exe" -ArgumentList "Script.vbs" -Wait -Passthru).ExitCode

Return Value from powershell to pipeline inside pipeline

In Jenkins Pipeline i want return a value from powershell to pipeline but i dont know how
Example:
pipeline {
agent any
stages {
stage('Return Value') {
steps {
parameters([
string(name: 'Value1'),
])
powershell '''
parameters for conection ...
extra parameters ....
$resultQuery= Invoke-Sqlcmd #conection -QueryTimeout 0 -ErrorAction Stop
$value1 = $resultQuery.code <# 1000 #>
$message = $resultQuery.message <# Some message #>
''')
}
}
stage('Another Step') {
steps {
//I want ... if ($value1 <= 1000)
// do something
}
}
}
}
}
Then i want return out of the powershell script the $value1 for use it in another step.
i try with $ENV but doesn't work
$ENV:Value1 = $resultQuery.code
any idea??
I've used this:
powershell('''
"env.PACKAGE_VERSION='$newversion'" | Out-File packageVersion.properties -Encoding ASCII
''')
later:
script {
load('packageVersion.properties')}
using the value:
echo("---- PACKAGE_VERSION: ${env.PACKAGE_VERSION} ----")
If you have a powershell script that just outputs the single piece of text you want, then you can use the returnStdout param to get that value back to the pipeline script:
steps {
script {
env.MY_RESULT = powershell(returnStdout: true, script:'echo hi')
}
echo "${env.MY_RESULT}" // prints "hi"
}
more here: https://www.jenkins.io/blog/2017/07/26/powershell-pipeline/
I'm not familiar with Jenkins but have you tried using Write-output $value1 or return $value1?
I found that in some of my powershell scripts, anything I output is captured and returned to the calling function.
Of course, you will need to somehow save the value on the Jenkins side to reuse it.
Another way would be to save the value to a file and read it from the file. You could do it using $value1 | out-file C:\temp\temp.txt and then read it using Get-Content C:\temp\temp.txt in a separate script.

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