Access function inside filesystemwatcher event function powershell - function

I want access a function inside filesystemwatcher created event function. I tried using a global function but i never see output on console.
#Script Parameters
param(
[Parameter(Mandatory=$True, position=1)]
[String]$path
)
#Global Function
function global:myFunction (){
Write-Host "myFunction"
}
#FileSystemWatcher properties
$fsw = New-Object System.IO.FileSystemWatcher
$fsw.Path = $path
$fsw.Filter = ""
$fsw.IncludeSubDirectories = $True
$fsw.EnableRaisingEvents = $True
#Created event function
Register-ObjectEvent -InputObject $fsw -EventName Created -Action{
$global:myFunction #trying to access global function
}

Your only problem is a syntax confusion about how to invoke a global function:
$global:myFunction # WRONG - looks for *variable*
looks for a variable named myFunction in the global scope.
Omit the $ to call the function:
global:myFunction # OK - calls function
That said, given that all scopes in a given session by default see global definitions, you don't need the global: scope specifier - simply invoke myFunction:
The only time you need global: explicitly is if there's a different myFunction definition in the current scope or in an ancestral scope, and you want to explicitly target the global definition.
Without global:, such a different definition would shadow (hide) the global definition.
To put it all together:
# Script Parameters
param(
[Parameter(Mandatory=$True, position=1)]
[String]$path
)
# Global Function
function global:myFunction {
param($FullName)
Write-Host "File created: $FullName"
}
# FileSystemWatcher properties
$fsw = New-Object System.IO.FileSystemWatcher
$fsw.Path = $path
$fsw.Filter = ""
$fsw.IncludeSubDirectories = $True
$fsw.EnableRaisingEvents = $True
# Created-event function
$eventJob = Register-ObjectEvent -InputObject $fsw -EventName Created -Action {
myFunction $EventArgs.FullPath # Call the global function.
}
Note that I've extended the code to pass the full filename of the newly created file to myFunction, via the automatic $EventArgs variable.
Alternatives:
Modifying the global scope from a script can be problematic due to the potential for name collisions, not least because global definitions linger even after the script terminates.
Therefore, consider:
either: moving the code of function myFunction directly into the -Action script block.
or: calling a (possibly temporary) script file from the -Action script block.
Also note that event action blocks typically write output to the success output stream, not directly to the host with Write-Host - if they need to produce output at all - where it can be collected on demand via the Receive-Job cmdlet.

You could specify explicitly your function inside Action scriptblock. Like so
#FileSystemWatcher properties
$fsw = New-Object System.IO.FileSystemWatcher
$fsw.Path = $path
$fsw.Filter = ""
$fsw.IncludeSubDirectories = $True
$fsw.EnableRaisingEvents = $True
#Created event function
Register-ObjectEvent -InputObject $fsw -EventName Created -Action {
#Describe your function here
function global:myFunction (){
Write-Host "myFunction"
}
#There call your function
global:myFunction
}

Related

How to call function outside a scriptblock

I have a function that I should be able to call from any place in my powershell script.
The problem is that it doesn't identify the function in a script block.
In the following example I have the function getNumberFive which should return the number 5.
I want to be able to use this function inside a scriptblock when I start a new job and also in the end of the script.
Expected result:
Write the number 15 to the file "C:\tmp\result.txt"
Write to the console: "I like the number 5"
In reality:
Write the number 10 to the file "C:\tmp\result.txt"
Write to the console: "I like the number 5"
I can workaround this issue by defining the same function inside the scriptblock but then I will duplicate the function and this is not a good programming.
Another way is to define:
$func = {
function getNumberFive(){
return 5
}
}
$scriptBlock = {
Function printSum(){
$number = getNumberFive
$newNumber = 10 + $number # => $newNumber should be 15
$newNumber >> "C:\tmp\result.txt"
}
}
Start-Job -ScriptBlock $scriptBlock -InitializationScript $func
But in this case I won't be able to call $five = getNumberFive.
I read number of methods but I didn't understand how exactly to use them:
CALLING A POWERSHELL FUNCTION IN A START-JOB SCRIPT BLOCK WHEN IT’S DEFINED IN THE SAME SCRIPT
How to pass a named function as a parameter (scriptblock)
https://social.technet.microsoft.com/Forums/ie/en-US/485df2df-1577-4770-9db9-a9c5627dd04a/how-to-pass-a-function-to-a-scriptblock?forum=winserverpowershell
PowerShell: Pass function as a parameter
Using Invoke-Command -ScriptBlock on a function with arguments
My script:
function getNumberFive(){
return 5
}
$scriptBlock = {
Function printSum(){
# $number = getNumberFive => DOESN'T WORK
# $number = Invoke-Expression ($(get-command getNumberFive) | Select -ExpandProperty Definition) => DOESN'T WORK AS EXPECTED
# $number = &(${function:getNumberFive}) => DOESN'T WORK AS EXPECTED
# $number = &(Get-Item function:getNumberFive) => DOESN'T WORK AS EXPECTED
$newNumber = 10 + $number # => $newNumber should be 15
$newNumber >> "C:\tmp\result.txt"
}
printSum
}
Start-Job -ScriptBlock $scriptBlock
$five = getNumberFive
Write-Host "I like the number"$five
Get-Job | Wait-Job
Get-Job | Stop-Job
Get-Job | Remove-Job
When you pass a scriptblock to start-job (or invoke-expression) the PowerShell instance that executes that scriptblock only has access to that scriptblock, anything that script block loads, and anything that already exists in the PowerShell instance.
Other parts of your script are not included. (For functions locally defined in your script to be available from other, possibly remote, instances of PowerShell the whole script – not just the scriptblock – and any dependencies would need to be accessible from the other instance.)
You could refactor the code you want in both places into a module which the script block loads as well as the job creating script.
When using jobs you are executing code in another process: like any remote operation the remote executing is a separate environment.
You can do like this:
function getNumberFive(){
return 5
}
$a={
Function printSum($number){
$newNumber = 10 + $number # => $newNumber should be 15
$newNumber >> "D:\result.txt"
}
}
$scriptBlock = {
param($number)
printSum -number $number
}
$five = getNumberFive
Write-Host "I like the number"$five
Start-Job -InitializationScript $a -ScriptBlock $scriptBlock -ArgumentList $five
There is a switch name -InitializationScript in Start-job where you can keep your initialization code like a function. Wrap it in a scriptblock and simply call it from the main scriptblock.
Hope it helps.

powershell add objects via -outvariable +variable in a function

I am running in an imported session window. Not sure if that matters.
I am trying to add a few variable values to an array in a function.
$Session = New-PsSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://emailserver/powershell/" -Authentication Kerberos
Import-PsSession $Session -allowclobber
Add-PsSnapin Microsoft.SharePoint.PowerShell
$group1 = "Accounting"
$group2 = "HR"
function AddUsersToGroups {
Write-output $Group1 -outvariable +Adgroups
Write-output $Group2 -outvariable +Adgroups
}
When I highlight and run the lines from a Powershell ISE they work fine and create the Variable $AdGroups and the combined data is in there.
But when I run the function all I see is the output and no variable gets created.
PS C:\Windows\system32> AddUserToGroups
Group1
Group2
Kinda Stumped. I tried to create a variable $AdGroups = #() as the first line but it fails also.
Not sure exactly what you are trying to achieve, but the AdGroups variable will be available only in the scope of the AddUsersToGroups function.
One way of solving this would be add the script: scope, like below:
Function AddUsersToGroups{
Write-output $Group1 -outvariable +script:Adgroups
Write-output $Group2 -outvariable +script:Adgroups
}
Now you should be able to access $Adgroups outside after the function is called.

What exactly is a PowerShell ScriptBlock

A PowerShell ScriptBlock is not a lexical closure as it does not close over the variables referenced in its declaring environment. Instead it seems to leverage dynamic scope and free variables which are bound at run time in a lambda expression.
function Get-Block {
$b = "PowerShell"
$value = {"Hello $b"}
return $value
}
$block = Get-Block
& $block
# Hello
# PowerShell is not written as it is not defined in the scope
# in which the block was executed.
function foo {
$value = 5
function bar {
return $value
}
return bar
}
foo
# 5
# 5 is written $value existed during the evaluation of the bar function
# it is my understanding that a function is a named scriptblock
# which is also registered to function:
Calling GetNewClosure() on a ScriptBlock returns a new ScriptBlock which closes over the variables referenced. But this is very limited in scope and ability.
What is a ScriptBlock's classification?
Per the docs, a scriptblock is a "precompiled block of script text." So by default you just a pre-parsed block of script, no more, no less. Executing it creates a child scope, but beyond that it's as if you pasted the code inline. So the most appropriate term would simply be "readonly source code."
Calling GetNewClosure bolts on a dynamically generated Module which basically carries a snapshot of all the variables in the caller's scope at the time of calling GetNewClosure. It is not a real closure, simply a snapshot copy of variables. The scriptblock itself is still just source code, and variable binding does not occur until it is invoked. You can add/remove/edit variables in the attached Module as you wish.
function GetSB
{
$funcVar = 'initial copy'
{"FuncVar is $funcVar"}.GetNewClosure()
$funcVar = 'updated value' # no effect, snapshot is taken when GetNewClosure is called
}
$sb = GetSB
& $sb # FuncVar is initial copy
$funcVar = 'outside'
& $sb # FuncVar is initial copy
$sb.Module.SessionState.PSVariable.Remove('funcVar')
& $sb # FuncVar is outside
A PowerShell ScriptBlock is equivalent to a first-class, anonymous function. Most of the confusion I've seen is not with ScriptBlocks, but with the function keyword.
PowerShell does support function closures, however the function keyword does not.
Examples
Function:
PS> function Hello {
>> param ([string] $thing)
>>
>> return ("Hello " + $thing)
>> }
PS> Hello "World"
"Hello World"
ScriptBlock:
PS> $HelloSB = {
>> param ([string] $thing)
>>
>> return ("Hello " + $thing)
>> }
PS> & $HelloSB "World"
"Hello World"
PS> $HelloRef = $HelloSB
PS> & $HelloRef "Universe"
"Hello Universe"
Closure:
PS> $Greeter = {
>> param ([string] $Greeting)
>>
>> return ( {
>> param ([string] $thing)
>>
>> return ($Greeting + " " + $thing)
>> }.GetNewClosure() )
>> }
PS> $Ahoy = (& $Greeter "Ahoy")
PS> & $Ahoy "World"
"Ahoy World"
PS> $Hola = (& $Greeter "Hola")
PS> & $Hola "Mundo"
"Hola Mundo"
Although you can get around the limitation of the function keyword with the "Set-Item" cmdlet:
PS> function Greeter = { ... } # ✕ Error
PS> function Greeter { ... }.GetNewClosure() # ✕ Error
PS> Set-Item -Path "Function:Greeter" -Value $Greeter # (defined above) ✓ OK
PS> $Hola = Greeter "Hola"
PS> & $Hola "Mundo"
"Hola Mundo"
The Value parameter of the "Set-Item" cmdlet can be any ScriptBlock, even one returned by another function. (The "Greeter" function, for example, returns a closure, as shown above.)
PS> Set-Item -Path "Function:Aloha" -Value (Greeter "Aloha")
PS> Aloha "World"
"Aloha World"
Two other important points:
PowerShell uses dynamic scoping, not lexical scoping.
A lexical closure is closed on its source-code environment, whereas a dynamic closure is closed based on the active/dynamic environment that exists when GetNewClosure() is called. (Which is more appropriate for a scripting language.)
PowerShell may have "functions" and "return" statements, but actually its input/output is based on streams and piping. Anything written out of a ScriptBlock with the "Write-Output" or "write" cmdlet will be returned.

Adding PowerShell functions to an object

I have a PowerShell module with a group of functions.
The function createService creates an instance of a service and returns a variable. Several of my functions use the returned value, but I only want one instance of the service so I cannot call createService in each function.
On the command line, I can do $var = createService($string), then call update($var) and it will work properly, but I don't want to force the user to remember to use $var as a parameter.
Is there a way to put these functions in an object/class so the variable can be stored globally and referenced inside each function instead of through parameters?
I would propose to start the service by the exposed functions, so that a user does even have to care of starting it.
$module = {
# The only service instance, $null so far
$script:service = $null
# Starts the service once and keeps its the only instance
function Start-MyService {
if ($null -eq $script:service) {
"Starting service"
$script:service = 'MyService'
}
}
# Ensures the service by Start-MyService and then operates on $script:service
function Update-MyService1 {
Start-MyService
"Updating service 1: $script:service"
}
# Ensures the service by Start-MyService and then operates on $script:service
function Update-MyService2 {
Start-MyService
"Updating service 2: $script:service"
}
Export-ModuleMember -Function Update-MyService1, Update-MyService2
}
$null = New-Module $module
# Starting service
# Updating service 1: MyService
Update-MyService1
# Updating service 2: MyService
Update-MyService2
In your module, if you assign the service object to a script scoped variable, all functions in the module can access the variable. Here is an example:
$module = {
function StartNewService {
$script:service = 'MyService'
}
function UpdateService {
"Updating service: " + $script:service
}
Export-ModuleMember -Function StartNewService, UpdateService
}
$null = New-Module $module
# StartNewService creates the service variable.
StartNewService
# UpdateService accesses the service variable created by StartNewService.
UpdateService
If you declare the variable as $global:service, you can access the variable from outside the module as well.
Edit: To address the comments below, here is a more practical example that shows an appropriate situation for sharing a variable among functions in a module. In this case all of the functions in the module depend on the same instance of the $Locations variable. In this example the variable is created outside of the functions, and is kept private by not including it in the Export-ModuleMember command.
Here is a simplified version of my LocationName.psm1
$Locations = #{}
function Save-LocationName {
param(
[parameter(Mandatory=$true)]
[string]$Name
)
$Locations[$Name] = $PWD
}
function Move-LocationName {
param(
[parameter(Mandatory=$true)]
[string]$Name
)
if($Locations[$Name]) {
Set-Location $Locations[$Name]
}
else {
throw ("Location $Name does not exist.")
}
}
New-Alias -Name svln -Value Save-LocationName
New-Alias -Name mvln -Value Move-LocationName
Export-ModuleMember -Function Save-LocationName, Move-LocationName -Alias svln, mvln
With this module a user can give a name to a directory, and move to that location by using the given name. For example if I am at \\server01\c$\Program Files\Publisher\Application\Logs, I can save the location by entering svln logs1. Now if I change my location, I can return to the logs directory with mvln logs1. In this example it would be impractical to use the locations hashtable for input and output since the functions are always working with the same instance.

How do I dynamically create functions that are accessible in a parent scope?

Here is an example:
function ChildF()
{
#Creating new function dynamically
$DynFEx =
#"
function DynF()
{
"Hello DynF"
}
"#
Invoke-Expression $DynFEx
#Calling in ChildF scope Works
DynF
}
ChildF
#Calling in parent scope doesn't. It doesn't exist here
DynF
I was wondering whether you could define DynF in such a way that it is "visible" outside of ChildF.
Another option would be to use the Set-Item -Path function:global:ChildFunction -Value {...}
Using Set-Item, you can pass either a string or a script block to value for the function's definition.
The other solutions are better answers to the specific question. That said, it's good to learn the most general way to create global variables:
# inner scope
Set-Variable -name DynFEx -value 'function DynF() {"Hello DynF"}' -scope global
# somewhere other scope
Invoke-Expression $dynfex
DynF
Read 'help about_Scopes' for tons more info.
You can scope the function with the global keyword:
function global:DynF {...}
A more correct and functional way to do this would be to return the function body as a script block and then recompose it.
function ChildF() {
function DynF() {
"Hello DynF"
}
return ${function:DynF}
}
$DynFEx = ChildF
Invoke-Expression -Command "function DynF { $DynFEx }"
DynF
Thanks to Richard's post. Kept having issues doing this simple thing. I revised for passing a function from local to remote.
#Method 1 Load the function from disk
$getCert = gc 'C:\MyScripts\getCert.ps1'
Invoke-Command $RemoteSrv -ScriptBlock {Set-Variable -name DefFN -value ($Args -join "`n") -scope global ; Invoke-Expression $DefFn } -ArgumentList $getCert
#Method 2 Load the function from local definition of function
Invoke-Command $RemoteSrv -ScriptBlock {Set-Variable -name DefFN -value ($Args -join "`n") -scope global ; Invoke-Expression $DefFn } -ArgumentList ('Function GetCert {'+(Get-Command GetCert).Definition+'}')
#Remote server now has function
Invoke-Command $RemoteSrv -ScriptBlock {getcert stackoverflow.com}
URL : stackoverflow.com
Expires : 12/14/2021 8:07:08 AM
SAN : DNS Name=*.askubuntu.com, DNS Name=.....
Thumbprint : ec0055be478411bafe98d11d63a5c9279ff0e173
IP : 151.101.193.69
Handle : 2866249748176
Issuer : CN=R3, O=Let's Encrypt, C=US
Subject : CN=*.stackexchange.com