How do you properly create objects with functions in Powershell? - powershell

We have a Powershell script which loads some configuration for our application from a json file via something like:
$ourApplicationSettings=Get-Content -Raw -Path $EnvironmentFile | ConvertFrom-Json
Now that I have an object which contains all of the settings, I'd like to create a handful of functions which can operate on either the $settings object directly, or some portion of it.
The best practices articles I've read for Powershell state that functions should be of the form: Verb-Noun, which sounds like developers would be expected to write functions like:
Get-OurAppSourceDirectory $ourApplicationSettings
DoSomething-OurApp $ourApplicationSettings
This seems very counter intuitive as it means that there is no way to easily find all of the functions associated with OurApp.
One article proposes one possible way would be to use a function like:
function New-OurAppConfig {
$appConfig = Get-Content -Raw -Path $EnvironmentFile | ConvertFrom-Json
$appConfig
}
but this way I'm not sure how to add member functions so that I could write:
$config = New-OurAppConfig
$config.Get-SrcDirectory
$config.Invoke-ActionABC

Well, you can search for that. Get-Command *ourapp*. So that one is not true. As for the second question:
$config = New-OutAppConfig
$var = Get-SrcDirectory $config
Invoke-ActionABC $var
or alternatively you can do a meta function that calls all those functions, so you could just call it once and thats it.
Also, it seems like what you are trying to achieve can be done with classes in PowerShell. https://blogs.technet.microsoft.com/heyscriptingguy/2015/09/04/adding-methods-to-a-powershell-5-class/

You can use Add-Member to add script methods to an existing object:
$foo = '{"value":23}' | ConvertFrom-Json
$foo | Add-Member -Type ScriptMethod -Name Multiply -Value {
Param($factor)
$this.value * $factor
}
$foo.Multiply(2) # output: 46
$foo.Multiply(3) # output: 69
That approach is a little awkward, though, both because PowerShell prior to v5 wasn't really built for full OO and because calling methods on a configuration object feels odd.
Generally it's more PoSh to read your configuration once at the beginning of your script and then either use it as a global singleton:
$cfg = Get-Content 'C:\path\to\your.json' -Raw | ConvertFrom-Json
function Get-Something {
...
Invoke-Other $cfg.Whatever
...
}
Get-Something
or make the configuration a mandatory parameter for your functions:
$cfg = Get-Content 'C:\path\to\your.json' -Raw | ConvertFrom-Json
function Get-Something {
Param(
[Parameter(Mandatory=$true)
$Config
)
...
Invoke-Other $Config.Whatever
...
}
Get-Something -Config $cfg

Related

Bulk-Assign PowerShell Variables using Multi-Level [PSCustomObject]

TLDR
I'm trying to create a function that will take a Multi-Level [PSCustomObject], extract the Key/Value pairs (strings only), and use them to declare Individual Global Variables using Set-Variable.
Current Code
Set-Variable -Name 'NSOneDrive' -Value "D:\OneDrive - New Spectrum"
$StrykerDirs = [PSCustomObject]#{
'OneDrive' = [PSCustomObject]#{
'NSOneDrive' = "D:\OneDrive - New Spectrum"
'MyOneDrive' = "D:\OneDrive"
}
'Dev' = [PSCustomObject]#{
'DevDir' = "${NSOneDrive}\Dev"
'DevToolsDir' = [PSCustomObject]#{
'DevTools' = "${NSOneDrive}\Dev\_DevTools"
'Terminals' = [PSCustomObject]#{
'DT_Terminals' = "${NSOneDrive}\Dev\_DevTools\terminals"
'DT_PowerShell' = "${NSOneDrive}\Dev\_DevTools\terminals\PowerShell"
}
'Editors' = [PSCustomObject]#{
'DT_Editors' = "${NSOneDrive}\Dev\_DevTools\.editors"
}
}
'ProjectsDir' = [PSCustomObject]#{
'NSProjects' = "${NSOneDrive}\Projects\NewSpectrum"
'MyProjects' = "${NSOneDrive}\Projects\Personal"
}
}
}
$StrykerDirs |
ConvertTo-JSON -Depth 25 |
Tee-Object -FilePath ".\JSON\Stryker-Paths.json"
function Set-DirAliases {
[CmdletBinding()]
# I might add parameters after I know how to make the 'Process' work
Begin {
# Begin Process Block
}
Process {
ForEach ( $dir in $StrykerDirs ) {
where ( $_.GetType() -eq 'String' ) |
Set-Variable -Name "${key}" -Value "${value}"
# I know ${key} and ${value} won't work, but I'm not sure how to properly fill them
}
}
End {
# End Process Block
}
}
Goals
Simplifying Set-Location Navigation
First and foremost I obviously need to figure out how to make the above Process block work. Once I do, I'll be able to easily declare Directory Variables for use with Set-Location. This is only for streamlining variable declarations so I don't have to repeatedly declare them with a messy barrage of individual Set-Variable commands while also avoiding the use of long (sometimes very long) $Object.PropertyName 'variables'.
After I get a handle on this script, I'll be able to finish several other scripts and functions that use (more or less) the same basic process.
Add to $PROFILE
This particular script is going to be part of a 'Startups' section in my default $PROFILE (Microsoft.PowerShell_profile.ps1) so I can set the Directory Variables in-bulk and keep the $PROFILE script itself nice and clean.
The other scripts that I mentioned anbove are also going to be included in my $PROFILE Startups.
JSON Output
The script also exports a .json file so that, among other things, I can (hopefully) repeat the process down the road in my WSL Bash Profiles.
Param() Functionality
Eventually I want to add a Param() block so the function can be used outside of the script as well.

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 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.

Best practice to use string data from a file

What is the best practice to create a text-based database for a PowerShell script?
What I really mean exactly?
I have a PowerShell script which use an url. This address may be used in other PS scripts, but I would be pretty happy if a quite simple practice is exists to solve to store this URL (or more) in a file, and the scripts use form this content, what I only have to define, which variable, line should be used, like this:
SharePointUrl = "..."
CcUrl = "..."
And in the script:
$SPUrl = DataFile.SharePointUrl ...
Something like this.
Not sure if I understand your question correctly. Are you looking for something like this?
$SharePointUrl = 'http://www.example.org/...'
New-Object -Type PSObject -Property #{'URL'=$SharePointUrl} |
Export-Csv 'C:\path\to\some.csv' -NoType
$DataFile = Import-Csv 'C:\path\to\some.csv'
$SPUrl = $DataFile.URL
Edit: After re-reading your question it seems you have an input file with key=value pairs. That can be processed like this:
$DataFile = Get-Content 'C:\path\to\data.txt' -Raw | ConvertFrom-StringData
$SPUrl = $DataFile.SharePointUrl
Another option is to write the configuration to a second PowerShell script:
# as variables
$SharePointUrl = "..."
$CcUrl = "..."
# or as a hashtable
$DataFile = #{
SharePointUrl = "..."
CcUrl = "..."
}
and dot-source that script in your original script.
. 'C:\path\to\config.ps1'

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