I load an "inline-type" in PowerShell like so:
$source = " some c# here "
$type = Add-Type -TypeDefinition $source -PassThru
$myvar = [MyClass]::new()
$myvar.Do()
How to call Write-Verbose from within the C# snippet, i.e., from the source in $source?
I only find examples on the Internet which perform WriteVerbose calls if the C# code contains a Cmdlet - which it does not in my case. Also I can find code how to call cmdlets in general, however, I don't want to create a new runspace, is there maybe any way to get the current runspace?
I found a solution, it is possible to pass a delegate to the C# code like so:
$writeVerboseDelegate = [Action[string]]{param($s) Write-Verbose $s; }
...
$myvar = [MyClass]::new($writeVerboseDelegate)
Related
Hi,
I wrote a nice debug function that provides the name of function from which it's called. The code is:
Function Write-Log {
$callerName = (Get-Variable MyInvocation -Scope 1).Value.MyCommand.Name
$stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$line = "$stamp [$level`]: $callerName $Message"
Write-Host $line -ForegroundColor $color
}
Works great when called from inside .ps1 script; however I started refactoring code so that some functions are moved to a .psm1 module - and this is where the function fails. Instead of printing the function name, it only provides the name of module.
How can I fix this function so that it only shows the function name, or scope & function name?
Thank you leeharvey1. Posting your suggestions as an answer to help other community members.
From your module, you can try using $callerName = "{0} - {1}:" -f (Get-PSCallStack)[1].Location, (Get-PSCallStack)[1].FunctionName.
I´m trying to reuse code on my SMA runbooks but everything I try to put inside a function doesn´t seem to work as expected.
For example, If I do this it works and returns the username of the credential:
workflow RB_Test
{
$credent = Get-AutomationPSCredential -Name "CRED_TESTE"
$var = $credent.Username
"result = ${var}"
}
Output:
But if I turn into this it doesn't work anymore (returns null):
workflow RB_Test
{
function FN_Test
{
$credent = Get-AutomationPSCredential -Name "CRED_TESTE"
$var = $credent.Username
"result = ${var}"
}
FN_Test
}
Output:
I've tried different things but without success. The debug/verbose screen don't return anything different. That also doesn't work:
Inlinescript {
. FN_Test
}
My goal would be to put several functions into a separate module and then import it on my runbooks for reusability but this really seems not to work.
This is a runbook (powershell workflow) created in the Service Management Automation (SMA).
I've read that there are some restrictions with Powershell workflow compared to pure Powershell but I am not sure if I am hitting one of them:
https://blogs.technet.microsoft.com/heyscriptingguy/2013/01/02/powershell-workflows-restrictions/
Thanks
Here's what I've had to do to get functions to work:
workflow FunctionTest {
function log {
param(
[string]$Message
)
Write-Output $Message
Write-Output "Filename: $Filename"
Write-Output "using:Filename: $using:Filename"
Write-Output "workflow:Filename: $workflow:Filename"
Write-Output "----"
## Under what conditions is 'global' used? Can't be used in a workflow...Hey Scripting Guy!
}
workflow DoSomething {
param(
[string]$Filename
)
log "Starting DoSomething"
}
$Filename = "LogFile_2017.csv"
log "Starting workflow"
## Variables need to be passed into workflow from parent-workflow
DoSomething -Filename $Filename
log "End workflow"
}
FunctionTest
I found you need to define your functions before using them. The tricky part was discovering that you have to pass your variables into the child-workflow.
The scope of the variables takes some getting used to.
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
Does PowerShell call any initialization code when a module is loaded?
I am looking for something like a Perl BEGIN block, or a constructor.
Both NEW-MODULE and IMPORT-MODULE will return a PSCustomObject. I am trying to encapsulate a custom object in a module to avoid lengthy code in scripts. One method that tests well in open code is:
$m = new-module -scriptblock {
New-Object PSCustomObject |
Add-Member NoteProperty -name person -value Frodo -passthru |
Add-Member ScriptMethod Who { $this.person } -passthru |
Add-Member ScriptMethod Mod {
param($x)
$this.person = $x
} -passthru
} -ascustomobject -returnresult
Ideally I would like to drop this code into a module and use something like:
$MyObj = Import-Module -Name ".\MyPackage" -AsCustomObject
and have MyObj be a handle to an object the same as the first snippet provides.
Suggestions appreciated.
It's not clear if you want to run initialization code when a module is loaded (like Perl's BEGIN block) or if you want to create a custom class (which is what "constructor" suggests).
Initialization code in a module is easy. Any code in a module not embedded in a function is executed when the module is imported.
Creating a custom class isn't supported natively in PS. But see: http://psclass.codeplex.com/. You can also write C#, VBScript, etc. and use Add-Type.
Import-module won't work to simulate a class, because you can only have 1 instance of a module with a given name - at best you'd have a singleton class. (BTW, import-module does have a -passthru parameter, which would more or less make your last line of code work - as a singleton. You'd also have to add export-module -variable * -function * to your module code) You could use New-Module to simulate a class though. And you could wrap it in a function named, new-myClass for example.
BTW, if you use the -ASCustomObject parameter you end up with a hashtable, which doesn't support "this" (in words hash table values that are script blocks don't have a built-in way to refer to the hashtable itself). If you use new-module without -AsCustomObject (and potentially use a factory function, for example new-myclass) then you could simulate "this.varInMyModule" with & $myModule $varInMyModule. However if you create a PSCustomObject, using Add-Member, then script method have access to $this and it in general acts a lot more like a typical object with properties and methods.
Modules are really supposed to output cmdlets, not objects. A module should provide a set of related cmdlets. There is a way to send data into the module using Import-Modules's -ArgumentList parameter as show here. You could use the technique to provide a server name for your cmdlets to connect to for example. The PowerCLI module handles that differently using a cmdlet that creates a script scope connection object ($script:connection) that the other cmdlets check for and re-use if it exists similar to this:
#test.psm1
$script:myvar = "hi"
function Show-MyVar {Write-Host $script:myvar}
function Set-MyVar ($Value) {$script:myvar = $Value}
#end test.psm1
Using Modules you can export both innate properties and functions, and don't need to run them through add-member or do much acrobatics. Note however that it has some issues if you don't want to export all properties and methods, and while you can initialize properties to an initial value, you CAN'T call an internal function during initialization without doing some akward acrobatics.
But I think what you really want to do is use Classes which are now available in Powershell 5 (they weren't when you posted). I've provided examples of each.
Sysops has a decent tutorial on the new classes in 4 parts
Here's the older way before powershell 5.0
# powershell 3.0 and above (I think)
$m = new-module -ascustomobject -scriptblock `
{
$person = "Frodo"
function Who
()
{return $this.person}
function Rename
($name)
{$this.person = $name}
Export-ModuleMember -Function * -Variable *
}
write-host "direct property access: $($m.person)"
write-host "method call: $($m.who())"
$m.Rename("Shelob")
write-host "change test: $($m.who())"
Also you can replicate multiple objects from a template like this:
# powershell 3.0 and above (I think)
$template = `
{
$person = "Frodo"
function Who
()
{return $this.person}
function Rename
($name)
{$this.person = $name}
Export-ModuleMember -Function * -Variable *
}
$me = new-module -ascustomobject -scriptblock $template; $me.Rename("Shelob")
$you = new-module -ascustomobject -scriptblock $template
"Me: $($me.Who())"
"You: $($you.Who())"
And in powershell 5 you have actual classes (mostly)
#requires -version 5
Class Person
{
hidden [String] $name #not actually private
[string] Who ()
{return $this.name}
[void] Rename ([string] $name)
{$this.name = $name}
# constructors are weird though, you don't specify return type OR explicitly return value.
Person ([String]$name)
{$this.name = $name}
<#
# The above constructor code is secretly converted to this
[Person] New ([string]$name) #note the added return type and renamed to New
{
$this.name = $name
return $this #note that we are returning ourself, you can exploit this to create chained constructors like [person]::New("gandalf").withWizardLevel("White") but I haven't done so here
}
#>
}
$me = [Person]::new("Shelob")
$you = [Person]::new("Frodo")
# $me|gm # Note that Name doesn't show here
# $me.name # But we can still access it...
# $me|gm -Force # Note that Name DOES show here
"`n"
"Me: $($me.who())"
"You: $($you.who())"
$you.Rename("Dinner")
"You after we meet: $($you.who())"
I have developed a PowerShell function that performs a number of actions involving provisioning SharePoint Team sites. Ultimately, I want the function to return the URL of the provisioned site as a String so at the end of my function I have the following code:
$rs = $url.ToString();
return $rs;
The code that calls this function looks like:
$returnURL = MyFunction -param 1 ...
So I am expecting a String, however it's not. Instead, it is an object of type System.Management.Automation.PSMethod. Why is it returning that type instead of a String type?
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around:
All output is captured, and returned
The return keyword really just indicates a logical exit point
Thus, the following two script blocks will do effectively the exact same thing:
$a = "Hello, World"
return $a
$a = "Hello, World"
$a
return
The $a variable in the second example is left as output on the pipeline and, as mentioned, all output is returned. In fact, in the second example you could omit the return entirely and you would get the same behavior (the return would be implied as the function naturally completes and exits).
Without more of your function definition I can't say why you are getting a PSMethod object. My guess is that you probably have something a few lines up that is not being captured and is being placed on the output pipeline.
It is also worth noting that you probably don't need those semicolons - unless you are nesting multiple expressions on a single line.
You can read more about the return semantics on the about_Return page on TechNet, or by invoking the help return command from PowerShell itself.
This part of PowerShell is probably the most stupid aspect. Any extraneous output generated during a function will pollute the result. Sometimes there isn't any output, and then under some conditions there is some other unplanned output, in addition to your planned return value.
So, I remove the assignment from the original function call, so the output ends up on the screen, and then step through until something I didn't plan for pops out in the debugger window (using the PowerShell ISE).
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled which will annoyingly spit a False out unless you make it [boolean]$isEnabled = $false.
Another good one is $someCollection.Add("thing") which spits out the new collection count.
With PowerShell 5 we now have the ability to create classes. Change your function into a class, and return will only return the object immediately preceding it. Here is a real simple example.
class test_class {
[int]return_what() {
Write-Output "Hello, World!"
return 808979
}
}
$tc = New-Object -TypeName test_class
$tc.return_what()
If this was a function the expected output would be
Hello World
808979
but as a class the only thing returned is the integer 808979. A class is sort of like a guarantee that it will only return the type declared or void.
As a workaround I've been returning the last object in the array that you get back from the function... It is not a great solution, but it's better than nothing:
someFunction {
$a = "hello"
"Function is running"
return $a
}
$b = someFunction
$b = $b[($b.count - 1)] # Or
$b = $b[-1] # Simpler
All in all, a more one-lineish way of writing the same thing could be:
$b = (someFunction $someParameter $andAnotherOne)[-1]
I pass around a simple Hashtable object with a single result member to avoid the return craziness as I also want to output to the console. It acts through pass by reference.
function sample-loop($returnObj) {
for($i = 0; $i -lt 10; $i++) {
Write-Host "loop counter: $i"
$returnObj.result++
}
}
function main-sample() {
$countObj = #{ result = 0 }
sample-loop -returnObj $countObj
Write-Host "_____________"
Write-Host "Total = " ($countObj.result)
}
main-sample
You can see real example usage at my GitHub project unpackTunes.
The existing answers are correct, but sometimes you aren't actually returning something explicitly with a Write-Output or a return, yet there is some mystery value in the function results. This could be the output of a builtin function like New-Item
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/9/2020 4:32 PM 132257575335253136
True
All that extra noise of the directory creation is being collected and emitted in the output. The easy way to mitigate this is to add | Out-Null to the end of the New-Item statement, or you can assign the result to a variable and just not use that variable. It would look like this...
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory | Out-Null
>> # -or-
>> $throwaway = New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True
New-Item is probably the more famous of these, but others include all of the StringBuilder.Append*() methods, as well as the SqlDataAdapter.Fill() method.
You need to clear output before returning. Try using Out-Null. That's how powershell return works. It returns not the variable you wanted, but output of your whole function. So your example would be:
function Return-Url
{
param([string] $url)
. {
$rs = $url.ToString();
return
} | Out-Null
return $rs
}
$result = Return-Url -url "https://stackoverflow.com/questions/10286164/function-return-value-in-powershell"
Write-Host $result
Write-Host $result.GetType()
And result is:
https://stackoverflow.com/questions/10286164/function-return-value-in-powershell
System.String
Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns
It's hard to say without looking at at code. Make sure your function doesn't return more than one object and that you capture any results made from other calls. What do you get for:
#($returnURL).count
Anyway, two suggestions:
Cast the object to string:
...
return [string]$rs
Or just enclose it in double quotes, same as above but shorter to type:
...
return "$rs"
Luke's description of the function results in these scenarios seems to be right on. I only wish to understand the root cause and the PowerShell product team would do something about the behavior. It seems to be quite common and has cost me too much debugging time.
To get around this issue I've been using global variables rather than returning and using the value from the function call.
Here's another question on the use of global variables:
Setting a global PowerShell variable from a function where the global variable name is a variable passed to the function
The following simply returns 4 as an answer. When you replace the add expressions for strings it returns the first string.
Function StartingMain {
$a = 1 + 3
$b = 2 + 5
$c = 3 + 7
Return $a
}
Function StartingEnd($b) {
Write-Host $b
}
StartingEnd(StartingMain)
This can also be done for an array. The example below will return "Text 2"
Function StartingMain {
$a = ,#("Text 1","Text 2","Text 3")
Return $a
}
Function StartingEnd($b) {
Write-Host $b[1]
}
StartingEnd(StartingMain)
Note that you have to call the function below the function itself. Otherwise, the first time it runs it will return an error that it doesn't know what "StartingMain" is.