Using script scope modifier on PowerShell functions in modules has no effect? - powershell

I assumed that using the script scope modifier on a function in a PowerShell module would prevent the function from being exported. Sample:
function script:Get-One { 1 }
When I import the module the Get-One function is exported.
Questions
Is it supposed to work to use the script scope modifier to make module functions private?
If not: Why? Any other scope modifiers that I can use?
I know I can use Export-ModuleMember to control which functions to export, but I only have a few functions that should not be exported. I would rather specify which functions to ignore.

script scope in a module is redundant because the default scope is the script/module in which it's defined. It is the equivalent of an instance variable. global in a module is akin to a static variable. Posh-Git for example uses a Global preferences variable for consistency between shells.
I used an in-memory module but the idea is the same when using a psd1 to define the arguments to New-Module
# remove module from namespace for repeated testing
if(Get-Module -Name 'SOTest') { Remove-Module -Name 'SOTest' }
new-Module -Function:'*' -Name:'SOTest' -ScriptBlock {
$global:helpers = [PSCustomObject]#{}
Add-Member -InputObject:$global:helpers -MemberType:ScriptMethod -Name:'HiddenFoo' -Value { return "Private Foo!" }
Add-Member -InputObject:$global:helpers -MemberType:ScriptMethod -Name:'HiddenFooWithArgs' -Value { return "Private " + $args -join ',' + '!' }
Add-Member -InputObject:$global:helpers -MemberType:ScriptMethod -Name:'HiddenFooWithParams' -Value { param ([string]$str, [int]$num); return "Private Str = $str + num; $num!" }
## script: scope refers to module scope, which makes it redundant
function WriteFoo {
Write-Output "PublicFoo!"
}
function WritePrivate {
#This fails, because the private now refers to the function scoped $private:helpers
$private:helpers.HiddenFoo | Write-Verbose -Verbose
}
function WritePrivateShort {
# This works because the helpers object is static across instances
$helpers.HiddenFoo() | Write-Verbose -Verbose
$helpers.HiddenFooWithArgs("Cow", "Moo") | Write-Verbose -Verbose
$helpers.HiddenFooWithParams("Four", 4) | Write-Verbose -Verbose
# This errors due to argument type mismatch
$helpers.HiddenFooWithParams("Five", "Five") | Write-Verbose -Verbose
}
} | Import-Module
# Prefer short errors for this demo
$ErrorView = "CategoryView"
Get-Module -Name 'SOTest'
'WF-----------------'
WriteFoo
'WP-----------------'
WritePrivate
'WPS----------------'
WritePrivateShort
'-------------------'
And the output:
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 SOTest {WriteFoo, WritePrivate, WritePrivateShort}
WF-----------------
PublicFoo!
WP-----------------
InvalidData: (:) [Write-Verbose], ParameterBindingValidationException
WPS----------------
VERBOSE: Private Foo!
VERBOSE: Private Cow Moo
VERBOSE: Private Str = Four + num = 4!
InvalidArgument: (:) [], RuntimeException
-------------------

For a Powershell module, Export-ModuleMember is the preferred way to do that.
As an alternative, you can define the function that you don't want exported from inside the function(s) which uses it (in the Begin block, for example).
This makes the function only visible by the "parent" function, making it effectively private.
Also, you could try using the Private scope , instead of the Script scope.

Related

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

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.

Difference between Set-Variable foo -Scope Global -Value "bar" and $global:foo = "bar"

What is the difference between
Set-Variable foo -Scope Global -Value "bar"
and
$global:foo = "bar"
I have seen that Set-Variable implements -WhatIf/-Confim so the assignment only happens if these are not set or the action is confirmed whereas the assignment happens regardless. Is there anything else lurking?
UPDATE
Following is an example of how the 2 are different in the presence of -Whatif / -Confirm:
Clear-Variable foo -ErrorAction:SilentlyContinue # just in case
Function repro {
[CmdletBinding(SupportsShouldProcess)] Param()
Set-Variable foo -Scope Global -Value "bar"
}
repro -WhatIf
Write-Host "`$global:foo=$($global:foo)"
You should see:
What if: Performing the operation "Set variable" on target "Name: foo Value: bar".
$global:foo=
However:
Clear-Variable foo -ErrorAction:SilentlyContinue # just in case
Function repro {
[CmdletBinding(SupportsShouldProcess)] Param()
$global:foo = "bar"
}
repro -WhatIf
Write-Host "`$global:foo=$($global:foo)"
Now you get:
$global:foo=bar
The difference isn't much but the Set-Variable has a few more options available to it than just a regular assignment expression. It's just a matter of how verbose you want to be with your syntax and what you are trying to accomplish.
Assignment Expressions
These are simple, quick variable assignments not much to it.
$foo = "bar"
# If you want to change scope, add it
$global:foo = "bar"
$script:foo = "bar"
Set-Variable CmdLet
As you've pointed out, the Set-Variable Cmdlet has a few more options with it, like -WhatIf and -Confirm. It also has a few other options like setting the type of variable with -Option and -PassThru. -Option allows for the setting of constants, readonly variables, or private scoped variables. -PassThru is pretty useful to and I'll give an example below. A couple more parameters are also available to so review them here
Why PassThru is cool
# PassThru executes your Set-Variable and then continues to
# pass the variable through the pipeline so with it you can
# assign your variable and then immediately start working with it
Set-Variable -Name "FooArr" -Value #("bar", "baz", "boom") -PassThru | ForEach-Object {
$_.Value
}
$FooArr # Still assigned
bar
baz
boom
Working with Constants
# Constant
Set-Variable -Name "ImAConstant" -Value "Try to delete me" -Option Constant
Remove-Variable -Name "ImAConstant"
# Error
Remove-Variable : Cannot remove variable ImAConstant because it is constant or
read-only. If the variable is read-only, try the operation again specifying the Force
option.
At line:2 char:1
+ Remove-Variable -Name "ImAConstant"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (ImAConstant:String) [Remove-Variable], Ses
sionStateUnauthorizedAccessException
+ FullyQualifiedErrorId : VariableNotRemovable,Microsoft.PowerShell.Commands.Remo
veVariableCommand
Working with Read only
Set-Variable -Name "ImReadOnly" -Value "Delete me" -Option ReadOnly
Remove-Variable -Name "ImReadOnly" -Force # Removed
These are just a few examples. Pretty cool stuff.
Update to OP Update
In your function repro, you use SupportsShouldProcess in your CmdLetBinding() but you still need to block it up for your assignment with some code for it to run the way you are thinking.
Function repro {
[CmdletBinding(SupportsShouldProcess)] Param()
if ($pscmdlet.ShouldProcess('$global:foo', "Sets Variable")){
$global:foo = "bar"
}
}
This will produce the same result as your original Set-Variable call with -WhatIf.

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

Can I write a class using PowerShell?

With PowerShell being built on top of the .NET framework, can I write my own custom class using PowerShell?
I'm not talking about instantiating .NET classes... that part is plain enough. I want to write my own custom classes using PowerShell scripts. Is it possible? So far my research leads me to say that this isn't possible.
I'm still learning PowerShell, and so far I haven't found an answer on this website, despite a few searches.
Take a look at the Add-Type cmdlet. It lets you write C# and other code in PowerShell.
For example (from the above link), in a PowerShell window,
$source = #"
public class BasicTest
{
public static int Add(int a, int b)
{
return (a + b);
}
public int Multiply(int a, int b)
{
return (a * b);
}
}
"#
Add-Type -TypeDefinition $source
[BasicTest]::Add(4, 3)
$basicTestObject = New-Object BasicTest
$basicTestObject.Multiply(5, 2)
I suspect that the solution that you are looking for is PowerShell Modules. They perform the roles that classes typically perform in other languages. They give you a very simple, yet structured, way to reuse your code.
Here is how to get the functionality of classes in PowerShell using modules. At the command line you could do this:
New-Module -ScriptBlock {function add($a,$b){return $a + $b}; function multiply($a,$b){return $a * $b}; function supersecret($a,$b){return multiply $a $b}; export-modulemember -function add, supersecret}
Then you would be able to:
PS C:\> add 2 4
6
PS C:\> multiply 2 4
The term 'multiply' 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:1 char:9
+ multiply <<<< 2 4
+ CategoryInfo : ObjectNotFound: (multiply:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
PS C:\> supersecret 2 4
8
As you can see multiply is private within the module. More traditionally you would instantiate an object that is an instance of the module. That is done via the -AsCustomObject parameter:
$m = New-Module -ScriptBlock {function add($a,$b){return $a + $b}; function multiply($a,$b){return $a * $b}; function supersecret($a,$b){return multiply $a $b}; export-modulemember -function add, supersecret} -AsCustomObject
Then you could:
PS C:\> $m.add(2,4)
6
PS C:\> $m.multiply(2,4)
Method invocation failed because [System.Management.Automation.PSCustomObject] doesn't contain a method named 'multiply'.
At line:1 char:12
+ $m.multiply <<<< (2,4)
+ CategoryInfo : InvalidOperation: (multiply:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
PS C:\> $m.supersecret(2,4)
8
This all demonstrates the use of dynamic modules meaning nothing is stored to disk for reuse. It is fine for very simple functionality. If you want to actually be able to read the code and reuse it in future sessions or scripts, however, you would want to store it in a .psm1 file and then store that file in a folder with the same name (minus the extension) as the file. Then you can import the module into your session at the command line or into another script.
As an example of this, let's say I took this code:
function Add{
param(
$a,
$b
)
return $a + $b
}
function Multiply{
param(
$a,
$b
)
return $a + $b
}
function SuperSecret{
param(
$a,
$b
)
return Multiply $a $b
}
Export-ModuleMember -Function Add, SuperSecret
And saved it to a file called TestModule.psm1 in the folder: C:\Windows\System32\WindowsPowerShell\v1.0\Modules\TestModule
The Modules folder in the PowerShell install folder is a magic folder and any modules stored there are visible to the Import-Module cmdlet without having to specify a path. Now if we run Get-Module -List at the command line we see:
ModuleType Name ExportedCommands
---------- ---- ----------------
Script DotNet {}
Manifest FileSystem {Get-FreeDiskSpace, New-Zip, Resolve-ShortcutFile, Mount-SpecialFolder...}
Manifest IsePack {Push-CurrentFileLocation, Select-CurrentTextAsVariable, ConvertTo-Short...
Manifest PowerShellPack {New-ByteAnimationUsingKeyFrames, New-TiffBitmapEncoder, New-Viewbox, Ne...
Manifest PSCodeGen {New-Enum, New-ScriptCmdlet, New-PInvoke}
Manifest PSImageTools {Add-CropFilter, Add-RotateFlipFilter, Add-OverlayFilter, Set-ImageFilte...
Manifest PSRss {Read-Article, New-Feed, Remove-Article, Remove-Feed...}
Manifest PSSystemTools {Test-32Bit, Get-USB, Get-OSVersion, Get-MultiTouchMaximum...}
Manifest PSUserTools {Start-ProcessAsAdministrator, Get-CurrentUser, Test-IsAdministrator, Ge...
Manifest TaskScheduler {Remove-Task, Get-ScheduledTask, Stop-Task, Add-TaskTrigger...}
Manifest WPK {Get-DependencyProperty, New-ModelVisual3D, New-DiscreteVector3DKeyFrame...
Manifest AppLocker {}
Manifest BitsTransfer {}
Manifest PSDiagnostics {}
Script **TestModule** {}
Manifest TroubleshootingPack {}
Manifest Citrix.XenApp.Commands... {}
We can see that our module is ready to import. We can import it into the session and use it in the raw using:
Import-Module TestModule
Or once again we can instantiate an object:
$m = Import-Module TestModule -AsCustomObject
You can use the class keyword that was introduced in PowerShell 5.0
Here is an example by Trevor Sullivan. (Archived here.)
##################################################
####### WMF 5.0 November 2014 Preview ###########
##################################################
class Beer {
# Property: Holds the current size of the beer.
[Uint32] $Size;
# Property: Holds the name of the beer's owner.
[String] $Name;
# Constructor: Creates a new Beer object, with the specified
# size and name / owner.
Beer([UInt32] $NewSize, [String] $NewName) {
# Set the Beer size
$this.Size = $NewSize;
# Set the Beer name
$this.Name = $NewName;
}
# Method: Drink the specified amount of beer.
# Parameter: $Amount = The amount of beer to drink, as an
# unsigned 32-bit integer.
[void] Drink([UInt32] $Amount) {
try {
$this.Size = $this.Size - $Amount;
}
catch {
Write-Warning -Message 'You tried to drink more beer than was available!';
}
}
# Method: BreakGlass resets the beer size to 0.
[void] BreakGlass() {
Write-Warning -Message 'The beer glass has been broken. Resetting size to 0.';
$this.Size = 0;
}
}
This works on Windows 10 Pro.
Test drive it like this:
# Create a new 33 centilitre beer, named 'Chimay'
$chimay = [Beer]::new(33, 'Chimay');
$chimay.Drink(10)
$chimay.Drink(10)
# Need more beer!
$chimay.Drink(200)
$chimay.BreakGlass()