How can you cascade a configuration file to submodules in powershell? [duplicate] - powershell

In the below sample module file, is there a way to pass the myvar value while importing the module.
For example,
import-module -name .\test.psm1 -?? pass a parameter? e.g value of myvar
#test.psm1
$script:myvar = "hi"
function Show-MyVar {Write-Host $script:myvar}
function Set-MyVar ($Value) {$script:myvar = $Value}
#end test.psm1
(This snippet was copied from another question.)

This worked for me:
You can use the –ArgumentList parameter of the import-module cmdlet to pass arguments when loading a module.
You should use a param block in your module to define your parameters:
param(
[parameter(Position=0,Mandatory=$false)][boolean]$BeQuiet=$true,
[parameter(Position=1,Mandatory=$false)][string]$URL
)
Then call the import-module cmdlet like this:
import-module .\myModule.psm1 -ArgumentList $True,'http://www.microsoft.com'
As may have already noticed, you can only supply values (no names) to –ArgumentList. So you should define you parameters carefully with the position argument.
Reference

The -ArgumentList parameter of Import-Module unfortunately does not accept a [hashtable] or [psobject] or something. A list with fixed postitions is way too static for my liking so I prefer to use a single [hashtable]-argument which has to be "manually dispatched" like this:
param( [parameter(Mandatory=$false)][hashtable]$passedVariables )
# this module uses the following variables that need to be set and passed as [hashtable]:
# BeQuiet, URL, LotsaMore...
$passedVariables.GetEnumerator() |
ForEach-Object { Set-Variable -Name $_.Key -Value $_.Value }
...
The importing module or script does something like this:
...
# variables have been defined at this point
$variablesToPass = #{}
'BeQuiet,URL,LotsaMore' -split ',' |
ForEach-Object { $variablesToPass[$_] = Get-Variable $_ -ValueOnly }
Import-Module TheModule -ArgumentList $variablesToPass
The above code uses the same names in both modules but you could of course easily map the variable names of the importing script arbitrarily to the names that are used in the imported module.

Related

Pass script variables to Powershell module

I guess this was answered a thousand times, but for love of all I can't find good (matching) answer to my problem.
I have a large PS script with a good dozen global variables that are used in various functions. For variables like $homedir I did not bother to include them in invocation of each function, since virtually all of them need to use it.
But now I need to write another script, and reuse ~80% of functions. Obviously I don't want to just copy&paste, since maintenance would be nightmare, so I told myself "let's finally learn to write PS modules" - basically cutting functions from the script and pasting them into module.
So far so good, but almost immediately I discovered that variables from the script are not passed to the module. I am not surprised by this, I just don't know what is the best practice to refactor my code (provided I don't really want to create functions with 10+ variables as input).
For now, I started adding necessary variables to each function, but the effect is that while before "working directory" variable was a given, now it has to be declared for each function. Hardly nice clean code there.
Is there a way to "init" a module, populating it with global variables?
EDIT:
Let's say I have a following code within a single script:
Function New-WorkDir {
if (Test-Path "$workDirectory") {
$null = Remove-Item -Recurse -Force $workDirectory
}
$null = New-Item -ItemType "directory" -Path "$workDirectory"
}
Function Set-Stage {
$null = New-Item -ItemType "file" -Force -Value $stage -Path "$workDirectory" -Name "ExecutionStage.txt"
}
$workDirectory = "C:\Temp"
New-WorkDir
$stage = "1"
Set-Stage
Now, I want to split the function be in a separate module. In order for this to work, I need to add function parameters explicitly, like so:
Function New-WorkDir {
Param(
[Parameter(Mandatory = $True, Position = 0)] [String] $WorkDirectory
)
if (Test-Path "$WorkDirectory") {
$null = Remove-Item -Recurse -Force $WorkDirectory
}
$null = New-Item -ItemType "directory" -Path "$WorkDirectory"
}
Function Set-Stage {
Param(
[Parameter(Mandatory = $True, Position = 0)] [String] $Stage,
[Parameter(Mandatory = $True, Position = 1)] [String] $WorkDirectory
)
$null = New-Item -ItemType "file" -Force -Value $Stage -Path "$WorkDirectory" -Name "ExecutionStage.txt"
}
And the main script becomes:
Import-Module new-module.psm1
$workDirectory = "C:\Temp"
New-WorkDir -WorkDirectory $workDirectory
$stage = "1"
Set-Stage -WorkDirectory $workDirectory -Stage $stage
So far, so good. My problem is that since virtually every function uses "$workDirectory", now I need to add an additional parameter to each of those functions, and what's worse - I need to add it everywhere in the code, severely impacting readability.
I was hoping that maybe there's some mechanism to "init" internal module variable, something like (pseudocode):
Import-Module new-module.psm1
Set-Variables -module new-module -WorkDirectory $workDirectory
$workDirectory = "C:\Temp"
New-WorkDir
$stage = "1"
Set-Stage -Stage $stage
Help, please?
While modules have state and you could set module variables through module functions that assign to $script:YourVariableName, I wouldn't recommend doing so. Although they are scoped to the module, module variables still smell like an anti-pattern similar to global variables. Having functions depend on state outside of the function makes maintenance and testing much harder. I recommend to use module variables only for constants.
A better pattern is to pass everything to the module functions via parameters. If it turns out that your functions have many common parameters, you could pass these via a single object parameter.
Say you have:
Function MyModuleFun1( $commonParam1, $commonParam2, $foo ) {
Write-Output $commonParam1 $commonParam2 $foo
}
Function MyModuleFun2( $commonParam1, $commonParam2, $bar ) {
Write-Output $commonParam1 $commonParam2 $bar
}
This could be refactored to...
Function MyModuleFun1( $commonParams, $foo ) {
Write-Output $commonParams.param1 $commonParams.param2 $foo
}
Function MyModuleFun2( $commonParams, $bar ) {
Write-Output $commonParams.param1 $commonParams.param2 $bar
}
... and called like this:
$common = [PSCustomObject]#{ param1 = 42; param2 = 21 }
MyModuleFun1 -commonParams $common -foo theFoo
MyModuleFun2 -commonParams $common -bar theBar
In this example the common parameter values are the same for all function calls, so we could use $PSDefaultParameterValues to pass them implicitly:
$PSDefaultParameterValues = #{
'MyModule*:commonParams' = [PSCustomObject]#{ param1 = 42; param2 = 21 }
}
MyModuleFun1 -foo theFoo
MyModuleFun2 -bar theBar
It is advisable to use a common prefix for all your module functions, to make sure that your $PSDefaultParameterValues don't leak into other functions. In my example all module functions start with prefix 'MyModule', so I could write MyModule*:commonParams to pass the common parameter values only to functions that start with 'MyModule' prefix.
For added type safety you could create a class for the common parameters within your module...
class MyModuleCommonParams {
[int] $param1
[String] $param2 = 'DefaultValue'
}
... and change your function signatures like this:
Function MyModuleFun1( [MyModuleCommonParams] $commonParams, $foo )
The function calls can stay the same, but now the function will check that only variables defined in the class, that have correct1 type, are passed:
$common = [PSCustomObject]#{ param1 = 42; xyz = 21 }
MyModuleFun1 -commonParams $common -foo theFoo
PowerShell will report an error, because the member xyz is not defined in class MyModuleCommonParams.
1 Actually it is sufficient that the argument type is convertible to the class member type. You could pass the string '42' to $param1, because it will be automatically converted to int.

Is there a way to show all functions in a PowerShell script?

Is there any command to list all functions I've created in a script?
Like i created function doXY and function getABC or something like this.
Then I type in the command and it shows:
Function doXY
Function getABC
Would be a cool feature^^
Thanks for all your help.
You can have PowerShell parse your script, and then locate the function definitions in the resulting Abstract Syntax Tree (AST).
Get-Command is probably the easiest way to access the AST:
# Use Get-Command to parse the script
$myScript = Get-Command .\path\to\script.ps1
$scriptAST = $myScript.ScriptBlock.AST
# Search the AST for function definitions
$functionDefinitions = $scriptAST.FindAll({
$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]
}, $false)
# Report function name and line number in the script
$functionDefinitions |ForEach-Object {
Write-Host "Function '$($_.Name)' found on line $($_.StartLineNumber)!"
}
You can also use this to analyze the functions' contents and parameters if necessary.
Where your script is named things.ps1, something like...
cat ./things.ps1 | grep function
For MacOS/Linux or...
cat ./things.ps1 | select-string function
For Windows.
This is a built-in feature as shown in the PowerShell help files.
About_Providers
Similar questions have been asked before. So, this is a potential duplicate of:
How to get a list of custom Powershell functions?
Answers... Using the PSDrive feature
# To get a list of available functions
Get-ChildItem function:\
# To remove a powershell function
# removes `someFunction`
Remove-Item function:\someFunction
Or
Function Get-MyCommands {
Get-Content -Path $profile | Select-String -Pattern "^function.+" | ForEach-Object {
[Regex]::Matches($_, "^function ([a-z.-]+)","IgnoreCase").Groups[1].Value
} | Where-Object { $_ -ine "prompt" } | Sort-Object
}
Or this one
Get List Of Functions From Script
$currentFunctions = Get-ChildItem function:
# dot source your script to load it to the current runspace
. "C:\someScript.ps1"
$scriptFunctions = Get-ChildItem function: | Where-Object { $currentFunctions -notcontains $_ }
$scriptFunctions | ForEach-Object {
& $_.ScriptBlock
}
As for this...
Thanks, this is kind of what i want, but it also shows functions like
A:, B:, Get-Verb, Clear-Host, ...
That is by design. If you want it another way, then you have to code that.
To get name of functions in any script, it has to be loaded into memory first, then you can dot source the definition and get the internals. If you just want the function names, you can use regex to get them.
Or as simple as this...
Function Show-ScriptFunctions
{
[cmdletbinding()]
[Alias('ssf')]
Param
(
[string]$FullPathToScriptFile
)
(Get-Content -Path $FullPathToScriptFile) |
Select-String -Pattern 'function'
}
ssf -FullPathToScriptFile 'D:\Scripts\Format-NumericRange.ps1'
# Results
<#
function Format-NumericRange
function Flush-NumberBuffer
#>
This function will parse all the functions included in a .ps1 file, and then will return objects for each function found.
The output can be piped directly into Invoke-Expression to load the retuned functions into the current scope.
You can also provide an array of desired names, or a Regular Expression to constrain the results.
My use case was I needed a way for loading individual functions from larger scripts, that I don't own, so I could do pester testing.
Note: only tested in PowerShell 7, but I suspect it will work in older versions too.
function Get-Function {
<#
.SYNOPSIS
Returns a named function from a .ps1 file without executing the file
.DESCRIPTION
This is useful where you have a blended file containing functions and executed instructions.
If neither -Names nor -Regex are provided then all functions in the file are returned.
Returned objects can be piped directly into Invoke-Expression which will place them into the current scope.
Returns an array of objects with the following
- .ToString()
- .Name
- .Parameters
- .Body
- .Extent
- .IsFilter
- .IsWorkFlow
- .Parent
.PARAMETER -Names
Array of Strings; Optional
If provided then function objects of these names will be returned
The name must exactly match the provided value
Case Insensitive.
.PARAMETER -Regex
Regular Expression; Optional
If provided then function objects with names that match will be returned
Case Insensitive
.EXAMPLE
Get all the functions names included in the file
Get-Function -name TestA | select name
.EXAMPLE
Import a function into the current scope
Get-Function -name TestA | Invoke-Expression
#>
param (
$File = "c:\fullpath\SomePowerShellScriptFile.ps1"
,
[alias("Name", "FunctionNames", "Functions")]
$Names
,
[alias("NameRegex")]
$Regex
) # end function
# get the script and parse it
$Script = Get-Command /Users/royomi/Documents/dev/javascript/BenderBot_AI/Import-Function.ps1
$AllFunctions = $Script.ScriptBlock.AST.FindAll({$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]}, $false)
# return all requested functions
$AllFunctions | Where-Object { `
( $Names -and $Names -icontains $_.Name ) `
-or ( $Regex -and $Names -imatch $Regex ) `
-or (-not $Names -and -not $Regex) `
} # end where-object
} # end function Get-Function

How to check command-line parameter from a PowerShell module?

Is there a way to check if a command-line parameter was specified for a PowerShell script from a module (.psm1 file)? I do not need the value, just have to know if the parameter was specified. The $PSBoundParameters.ContainsKey method does not seem to work.
TestParam.psm1:
function Test-ScriptParameter {
[CmdletBinding()]
param ()
# This does not work (always returns false):
return $PSBoundParameters.ContainsKey('MyParam')
}
Export-ModuleMember -Function *
TestParam.ps1:
[CmdletBinding()]
param (
$MyParam= "Default"
)
$path = Join-Path (Split-Path -Path $PSCommandPath -Parent) 'TestParam.psm1'
Import-Module $path -ErrorAction Stop -Force
Test-ScriptParameter
This must return false:
PS>.\TestParam.ps1
This must return true:
PS>.\TestParam.psq -MyParam ""
This must return true:
PS>.\TestParam.ps1 -MyParam "Runtime"
It cannot be done like you are thinking about it. The PSBoundParameters variable is native to the cmdlet's execution and as such depends on the param block of the cmdlet's definition. So in your case, the Test-ScriptParameter is checking if it was invoked with the parameter MyParam but since it doesn't specify it, then it will be always false.
To achieve what I believe you want, you need to create a function that checks into a hash structure like the PSBoundParameters for a specific key. The key needs to be provided by name. But then a simple $PSBoundParameters.ContainsKey('MyParam') wherever you need it should suffice.
The problem with your code is that you are checking the $PSBoundParameters value of the Function itself, which has no parameters.
You could make the function work by sending the $PSBoundParameters variable from the script in to the function via a differently named parameter.
For example:
TestParam.psm1:
function Test-ScriptParameter ($BoundParameters) {
return $BoundParameters.ContainsKey('MyParam')
}
Export-ModuleMember -Function *
TestParam.ps1:
[CmdletBinding()]
param (
$MyParam = "Default"
)
$path = Join-Path (Split-Path -Path $PSCommandPath -Parent) 'TestParam.psm1'
Import-Module $path -ErrorAction Stop -Force
Test-ScriptParameter $PSBoundParameters

Cannot modify a script-scoped variable from inside a function

I am currently making a script which is supposed to connect to 42 different local servers and getting the Users of a specific group (fjärrskrivbordsanvändare(Remote desktop users in swedish :D)) from active directory. After it has gotten all the users from the server it has to export the users to a file on MY desktop
The csv file has to look like this:
Company;Users
LawyerSweden;Mike
LawyerSweden;Jennifer
Stockholm Candymakers;Pedro
(Examples)
etc.
Here's the code as of now:
cls
$MolnGroup = 'fjärrskrivbordsanvändare'
$ActiveDirectory = 'activedirectory'
$script:CloudArray
Set-Variable -Name OutputAnvandare -Value ($null) -Scope Script
Set-Variable -Name OutputDomain -Value ($null) -Scope Script
function ReadInfo {
Write-Host("A")
Get-Variable -Exclude PWD,*Preference | Remove-Variable -EA 0
if (Test-Path "C:\file\frickin\path.txt") {
Write-Host("File found")
}else {
Write-Host("Error: File not found, filepath might be invalid.")
Exit
}
$filename = "C:\File\Freakin'\path\super.txt"
$Headers = "IPAddress", "Username", "Password", "Cloud"
$Importedcsv = Import-csv $filename -Delimiter ";" -Header $Headers
$PasswordsArray += #($Importedcsv.password)
$AddressArray = #($Importedcsv | ForEach-Object { $_.IPAddress } )
$UsernamesArray += #($Importedcsv.username)
$CloudArray += #($Importedcsv.cloud)
GetData
}
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
# (If the customer has cloud-service on server, proceed)
if($CloudArray[$row] -eq 1)
{
# Code below uses the information read in from a file to connect pc to server(s)
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
# Runs command on server
$OutputAnvandare = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$Anvandare.Name
}
}
$OutputDomain = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
gc env:UserDomain
}
}
$OutputDomain + $OutputAnvandare
}
}
}
function Export {
Write-Host("C")
# Variabler för att bygga up en CSV-fil genom Out-File
$filsökväg = "C:\my\file\path\Coolkids.csv"
$ColForetag = "Company"
$ColAnvandare = "Users"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $AA.Length; $p++) {
# writes out columns in the csv file
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
# Writes out the domain name and the users
$OutputDomain + $delimiter + $OutputAnvandare | Out-File $filsökväg -Append
}
}
ReadInfo
Export
My problem is, I can't export the users or the domain. As you can see i tried to make the variables global to the whole script, but $outputanvandare and $outputdomain only contains the information i need inside of the foreach loop. If I try to print them out anywhere else, they're empty?!
This answer focuses on variable scoping, because it is the immediate cause of the problem.
However, it is worth mentioning that modifying variables across scopes is best avoided to begin with; instead, pass values via the success stream (or, less typically, via by-reference variables and parameters ([ref]).
To expound on PetSerAl's helpful comment on the question: The perhaps counter-intuitive thing about PowerShell variable scoping is that:
while you can see (read) variables from ancestral (higher-up) scopes (such as the parent scope) by referring to them by their mere name (e.g., $OutputDomain),
you cannot modify them by name only - to modify them you must explicitly refer to the scope that they were defined in.
Without scope qualification, assigning to a variable defined in an ancestral scope implicitly creates a new variable with the same name in the current scope.
Example that demonstrates the issue:
# Create empty script-level var.
Set-Variable -Scope Script -Name OutputDomain -Value 'original'
# This is the same as:
# $script:OutputDomain = 'original'
# Declare a function that reads and modifies $OutputDomain
function func {
# $OutputDomain from the script scope can be READ
# without scope qualification:
$OutputDomain # -> 'original'
# Try to modify $OutputDomain.
# !! Because $OutputDomain is ASSIGNED TO WITHOUT SCOPE QUALIFICATION
# !! a NEW variable in the scope of the FUNCTION is created, and that
# !! new variable goes out of scope when the function returns.
# !! The SCRIPT-LEVEL $OutputDomain is left UNTOUCHED.
$OutputDomain = 'new'
# !! Now that a local variable has been created, $OutputDomain refers to the LOCAL one.
# !! Without scope qualification, you cannot see the script-level variable
# !! anymore.
$OutputDomain # -> 'new'
}
# Invoke the function.
func
# Print the now current value of $OutputDomain at the script level:
$OutputDomain # !! -> 'original', because the script-level variable was never modified.
Solution:
There are several ways to add scope qualification to a variable reference:
Use a scope modifier, such as script in $script:OutputDomain.
In the case at hand, this is the simplest solution:
$script:OutputDomain = 'new'
Note that this only works with absolute scopes global, script, and local (the default).
A caveat re global variables: they are session-global, so a script assigning to a global variable could inadvertently modify a preexisting global variable, and, conversely, global variables created inside a script continue to exist after the script terminates.
Use Get/Set-Variable -Scope, which - in addition to supporting the absolute scope modifiers - supports relative scope references by 0-based index, where 0 represents the current scope, 1 the parent scope, and so on.
In the case at hand, since the script scope is the next higher scope,
Get-Variable -Scope 1 OutputDomain is the same as $script:OutputDomain, and
Set-Variable -Scope 1 OutputDomain 'new' equals $script:OutputDomain = 'new'.
(A rarely used alternative available inside functions and trap handlers is to use [ref], which allows modifying the variable in the most immediate ancestral scope in which it is defined: ([ref] $OutputDomain).Value = 'new', which, as PetSerAl points out in a comment, is the same as (Get-Variable OutputDomain).Value = 'new')
For more information, see:
Get-Help about_Variables
Get-Help about_Scopes
Finally, for the sake of completeness, Set-Variable -Option AllScope is a way to avoid having to use scope qualification at all (in all descendent scopes), because effectively then only a single variable by that name exists, which can be read and modified without scope qualification from any (descendent) scope.
# By defining $OutputDomain this way, all descendent scopes
# can both read and assign to $OutpuDomain without scope qualification
# (because the variable is effectively a singleton).
Set-Variable -Scope Script -Option AllScope -Name OutputDomain
However, I would not recommend it (at least not without adopting a naming convention), as it obscures the distinction between modifying local variables and all-scope variables:
in the absence of scope qualification, looking at a statement such as $OutputDomain = 'new' in isolation, you cannot tell if a local or an all-scope variable is being modified.
Since you've mentioned that you want to learn, I hope you'll pardon my answer, which is a bit longer than normal.
The issue that's impacting you here is PowerShell Variable Scoping. When you're commiting the values of $outputAvandare and $outputDomain, they only exist for as long as that function is running.
Function variables last until the function ends.
Script variables last until the script ends.
Session/global variables last until the session ends.
Environmental variable persist forever.
If you want to get the values out of them, you could make them Global variables instead, using this syntax:
$global:OutputAnvandare = blahblahblah
While that would be the easiest fix for your code, Global variables are frowned upon in PowerShell, since they subvert the normal PowerShell expectations of variable scopes.
Much better solution :)
Don't be dismayed, you're actually almost there with a really good solution that conforms to PowerShell design rules.
Today, your GetData function grabs the values that we want, but it only emits them to the console. You can see this in this line on GetData:
$OutputDomain + $OutputAnvandare
This is what we'd call emitting an object, or emiting data to the console. We need to STORE this data instead of just writing it. So instead of simply calling the function, as you do today, do this instead:
$Output = GetData
Then your function will run and grab all the AD Users, etc, and we'll grab the results and stuff them in $output. Then you can export the contents of $output later on.

Powershell module initialization

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())"