Encapsulating Powershell Functions - function

I am at a lost as to how to perform the following task. As with class members in OOP, we are allowed to hide away implementation with a private modifier. My goal is to create a base powershell function that contains logic that is used by several functions for code reuse while hiding away that function from global access. According to the following reference https://ss64.com/ps/syntax-scopes.html , the following scopes are available Global, Script, and Private. My labeling of the functions do not produce the desired result. The ecapsulated function should work as shown below.
function Invoke-VMDoSomething {
Invoke-PrivateMiniFunc
}
function Invoke-VMDoSomethingElse {
Invoke-PrivateMiniFunc
}
function Invoke-PrivateMiniFunc {
###BaseReuseable code
}
Hypothetical Command Prompt
PS > Invoke-VMDoSomething <<<Invoke-PrivateMiniFunc Executes successfully
PS > Invoke-VMDoSomethingElse <<<Invoke-PrivateMiniFunc Executes successfully
PS > Invoke-PrivateMiniFunc <<<Fails Cannot find Such Commandlet -- Desired result.
How can I implement this convention and do I need to store the functions in a .psm1 file vice a ps1 file? Is it even possible?

Maybe not exactly what you are after, but you can hide functions inside a module.
In your case, create a new file and save it as *.psm1 (for demo I call it InvokeModule.psm1)
function Invoke-VMDoSomething {
Invoke-PrivateMiniFunc
}
function Invoke-VMDoSomethingElse {
Invoke-PrivateMiniFunc
}
function Invoke-PrivateMiniFunc {
Write-Host "Called by: $((Get-PSCallStack)[1].FunctionName)"
}
# export the functions you want to make available and leave out
# the functions you want to keep hidden (but available to the functions in the module)
Export-ModuleMember -Function Invoke-VMDoSomething, Invoke-VMDoSomethingElse
The last command Export-ModuleMember defines what functions you want exposed en what not.
Next, in another file import that module.
In there, only the Exported functions are visible/callable but the Invoke-PrivateMiniFunc is not:
Import-Module 'D:\InvokeModule.psm1'
Invoke-VMDoSomething # works as expected
Invoke-VMDoSomethingElse # works as expected
Invoke-PrivateMiniFunc # errors out
Result:
Called by: Invoke-VMDoSomething
Called by: Invoke-VMDoSomethingElse
Invoke-PrivateMiniFunc : The term 'Invoke-PrivateMiniFunc' 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.
At line:7 char:1
+ Invoke-PrivateMiniFunc
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Invoke-PrivateMiniFunc:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException

Related

How to refer to a PowerShell function defined in a parent scope?

I'm writing a PowerShell script that runs a couple of background jobs. Some of these background jobs will use the same set of constants or utility functions, like so:
$FirstConstant = "Not changing"
$SecondConstant = "Also not changing"
function Do-TheThing($thing)
{
# Stuff
}
$FirstJob = Start-Job -ScriptBlock {
Do-TheThing $using:FirstConstant
}
$SecondJob = Start-Job -ScriptBlock {
Do-TheThing $using:FirstConstant
Do-TheThing $using:SecondConstant
}
If I wanted to share variables (or, in this case, constants) in child scopes, I'd prefix the variable references with $using:. I can't do that with functions, though; running this code as-is returns an error:
The term 'Do-TheThing' is not recognized as the name of a cmdlet, function, script file, or operable program.
My question is this: How can my background jobs use a small utility function that I've defined in a higher scope?
If the function in the higher scope is in the same (non-)module scope in the same session, your code implicitly sees it, due to PowerShell's dynamic scoping.
However, background jobs run in a separate process (child process), so anything from the caller's scope must be passed explicitly to this separate session.
This is trivial for variable values, with the $using: scope, but less obvious for functions, but it can be made to work with a bit of duplication, by passing a function's body via namespace variable notation:
# The function to call from the background job.
Function Do-TheThing { param($thing) "thing is: $thing" }
$firstConstant = 'Not changing'
Start-Job {
# Define function Do-TheThing here in the background job, using
# the caller's function *body*.
${function:Do-TheThing} = ${using:function:Do-TheThing}
# Now call it, with a variable value from the caller's scope
Do-TheThing $using:firstConstant
} | Receive-Job -Wait -AutoRemoveJob
The above outputs 'thing is: Not changing', as expected.

TclOO : What is the difference between my and self?

The documentation probably explains it very well but I do not see the difference between this 2 commands in my case :
method dir {} {
puts "method dir..."
}
method pseudomethod {} {
set vardir [my dir]
set vardir [[self] dir]
}
The only difference I can see is that with [self] I can pass it as an argument in a procedure and not with my.
What is the best solution in my case ?
Both solutions have equal performance ?
The self command (with no extra arguments) is equivalent to self object which returns the current public name of the object that is executing the method (you can rename the object). The self command overall provides access to bits of “runtime” state.
The my command is actually the object's internal name; it's created in each object's instance namespace. You can invoke all exported and non-exported methods via my, unlike with the public name. This makes it useful for both calling your internal methods directly, and also for setting up things like callbacks to internal methods (you'll need something like namespace which or namespace code when setting up the callback).
Unlike with the public name, you can delete the internal name command without automatically destroying the object. It'll likely break code (your methods most probably) if you do that, but the base system allows you to do it.
Aside: Tcl 8.7 includes this helper procedure (which also works in 8.6) for creating callback scripts within methods (the funny name means it gets mapped into your methods automatically as callback):
proc ::oo::Helpers::callback {method args} {
list [uplevel 1 {::namespace which my}] $method {*}$args
}
In this case, if the callback was exported, you'd be able to do this instead:
proc ::oo::Helpers::callback {method args} {
list [uplevel 1 self] $method {*}$args
}
but that would be more vulnerable to rename problems. (In all cases, the uplevel 1 is because we want to run a little bit of name-resolving code in the calling context, not inside the scope of the procedure itself.)
I'm not sure how they are implemented, but one reason you'd want to use my is to access non-exported (private) methods. A demo:
oo::class create Foo {
method PrivateMethod {} {puts "this is PrivateMethod"}
method publicMethod {} {puts "this is publicMethod"}
method test {} {
my publicMethod
my PrivateMethod
[self] publicMethod
[self] PrivateMethod
}
}
then:
% Foo create foo
::foo
% foo test
this is publicMethod
this is PrivateMethod
this is publicMethod
unknown method "PrivateMethod": must be destroy, publicMethod or test
my is the mechanism for an object to invoke its methods.
self is the mechanism for introspection on how the current method was called.
Spend some time with the my and self man pages.

Gradle - using a function from within Ant

I have a Gradle script with few invocations of XJC to generate JAXB classes from XSD.
I thought I could parametrize these invocations and reuse the common code.
So I created a function:
ext.generateJaxbClasses = { HashMap params ->
project.ant {
...
And then I wanted to use it:
task genJaxb {
ext.generic = [
schema: "..."
]
doLast() {
ext.generateJaxbClasses(jaxbSetA)
ext.generateJaxbClasses(jaxbSetB)
}
But I get this error:
> No signature of method: org.gradle.internal.extensibility.DefaultExtraPropertiesExtension.generateJaxbClasses() is applicable for argument types: (LinkedHashMap) values: [[...]]
How can I use the function within a task definition?
Use project.ext.generateJaxbClasses(jaxbSetA)
Using ext inside the task resolves to searching for the property inside the task's extension container.
See ExtensionAware.
I would suggest using an actual free function inside the project rather than using extensions as it can lead to this kind of frustration.
def generateJaxbClasses(HashMap params) {
project.ant {...}
}

Supporting lexical-scope ScriptBlock parameters (e.g. Where-Object)

Consider the following arbitrary function and test cases:
Function Foo-MyBar {
Param(
[Parameter(Mandatory=$false)]
[ScriptBlock] $Filter
)
if (!$Filter) {
$Filter = { $true }
}
#$Filter = $Filter.GetNewClosure()
Get-ChildItem "$env:SYSTEMROOT" | Where-Object $Filter
}
##################################
$private:pattern = 'T*'
Get-Help Foo-MyBar -Detailed
Write-Host "`n`nUnfiltered..."
Foo-MyBar
Write-Host "`n`nTest 1:. Piped through Where-Object..."
Foo-MyBar | Where-Object { $_.Name -ilike $private:pattern }
Write-Host "`n`nTest 2:. Supplied a naiive -Filter parameter"
Foo-MyBar -Filter { $_.Name -ilike $private:pattern }
In Test 1, we pipe the results of Foo-MyBar through a Where-Object filter, which compares the objects returned to a pattern contained in a private-scoped variable $private:pattern. In this case, this correctly returns all the files/folders in C:\ which start with the letter T.
In Test 2, we pass the same filtering script directly as a parameter to Foo-MyBar. However, by the time Foo-MyBar gets to running the filter, $private:pattern is not in scope, and so this returns no items.
I understand why this is the case -- because the ScriptBlock passed to Foo-MyBar is not a closure, so does not close over the $private:pattern variable and that variable is lost.
I note from comments that I previously had a flawed third test, which tried to pass {...}.GetNewClosure(), but this does not close over private-scoped variables -- thanks #PetSerAl for helping me clarify that.
The question is, how does Where-Object capture the value of $private:pattern in Test 1, and how do we achieve the same behaviour in our own functions/cmdlets?
(Preferably without requiring the caller to have to know about closures, or know to pass their filter script as a closure.)
I note that, if I uncomment the $Filter = $Filter.GetNewClosure() line inside Foo-MyBar, then it never returns any results, because $private:pattern is lost.
(As I said at the top, the function and parameter are arbitrary here, as a shortest-form reproduction of my real problem!)
The example given does not work because calling a function will enter a new scope by default. Where-Object will still invoke the filter script without entering one, but the scope of the function does not have the private variable.
There's three ways around this.
Put the function in a different module than the caller
Every module has a SessionState which has its own stack of SessionStateScopes. Every ScriptBlock is tied to the SessionState is was parsed in.
If you call a function defined in a module, a new scope is created within that module's SessionState, but not within the top level SessionState. Therefore when Where-Object invokes the filter script without entering a new scope, it does so on the current scope for the SessionState to which that ScriptBlock is tied.
This is a bit fragile, because if you want to call that function from your module, well you can't. It'll have the same issue.
Call the function with the dot source operator
You most likely already know the dot-source operator (.) for invoking script files without creating a new scope. That also works with command names and ScriptBlock objects.
. { 'same scope' }
. Foo-MyBar
Note, however, that this will invoke the function within the current scope of the SessionState that the function is from, so you cannot rely on . to always execute in the caller's current scope. Therefore, if you invoke functions associated with a different SessionState with the dot-source operator - such as functions defined in a (different) module - it may have unintended effects. Variables created will persist to future function invocations and any helper functions defined within the function itself will also persist.
Write a Cmdlet
Compiled commands (cmdlets) do not create a new scope when invoked. You can also use similar API's to what Where-Object use (though not the exact same ones)
Here's a rough implementation of how you could implement Where-Object using public API's
using System.Management.Automation;
namespace MyModule
{
[Cmdlet(VerbsLifecycle.Invoke, "FooMyBar")]
public class InvokeFooMyBarCommand : PSCmdlet
{
[Parameter(ValueFromPipeline = true)]
public PSObject InputObject { get; set; }
[Parameter(Position = 0)]
public ScriptBlock FilterScript { get; set; }
protected override void ProcessRecord()
{
var filterResult = InvokeCommand.InvokeScript(
useLocalScope: false,
scriptBlock: FilterScript,
input: null,
args: new[] { InputObject });
if (LanguagePrimitives.IsTrue(filterResult))
{
WriteObject(filterResult, enumerateCollection: true);
}
}
}
}
how does Where-Object capture the value of $private:pattern in Test 1
As can be seen in the source code for Where-Object in PowerShell Core, PowerShell internally invokes the filter script without confining it to its own local scope (_script is the private backing field for the FilterScript parameter, notice the useLocalScope: false argument passed to DoInvokeReturnAsIs()):
protected override void ProcessRecord()
{
if (_inputObject == AutomationNull.Value)
return;
if (_script != null)
{
object result = _script.DoInvokeReturnAsIs(
useLocalScope: false, // <-- notice this named argument right here
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe,
dollarUnder: InputObject,
input: new object[] { _inputObject },
scriptThis: AutomationNull.Value,
args: Utils.EmptyArray<object>());
if (_toBoolSite.Target.Invoke(_toBoolSite, result))
{
WriteObject(InputObject);
}
}
// ...
}
how do we achieve the same behaviour in our own functions/cmdlets?
We don't - DoInvokeReturnAsIs() (and similar scriptblock invocation facilities) are marked internal and can therefore only be invoked by types contained in the System.Management.Automation assembly

Powershell alias inside function not available outside

I have a powershell function inside a file myfunc.ps1
function Set-Util ($utilPath ) {
if(Test-Path($utilPath) ){
$fullPath = Join-Path -Path $utilPath "util.exe"
set-alias MyUtil $fullPath
#echo "Path set to $fullPath"
}else {
throw (" Error: File not found! File path '$fullPath' does not exist.")
}
}
and I dot call it from command line with
. .\myfunc.ps1
then call
Set-Util somedirectory
The alias is being set correctly in the function, but I can't access it out here with
MyUtil
Should I export the alias because the scope is only in the method?
I tried doing so with
Export-ModuleMember but got an error saying the cmdlet can only be called from insdie a module.
You can't do that because the alias won’t be set until the function is called, and when the function is called the alias is scoped to the function scope, so when the function finishes running - the alias is gone.
If you want the alias to survive, specify use scope parameter with a value of 'global'
Aliases can only point to cmdlets, not to output of cmdlets (objects).