I'm creating a PowerShell module that I would like to use enums across several functions (that will be exported, if that matters) and classes in the module. The only way I can see to do this is to put all of the code in a single file. That's not an easy maintenance story, however. I want a module file structure similar to the following:
As you can see in the code, Class1 has the Enum1 property flagged as "Unable to find type [Enum1]."
Deploy-MyResource:
function Deploy-MyResource {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[Enum1] $EnumParam,
[Parameter(Mandatory=$true)]
[Class1] $ClassParam
)
$class2 = [Class2]::new()
if($class2.Prop1 -eq $ClassParam.Prop1) {
Write-Output "Matches"
}
else {
Write-Output "No match."
}
}
Class1:
Class Class1 {
[string] $Prop1
[int] $Prop2
[Enum1] $EnumProp
DoSomething() {
Write-Output "Did something using $($this.Prop1), $($this.Prop2), & $($this.EnumProp)"
}
}
Enum1:
enum Enum1 {
ValueOne
ValueTwo
}
Here are my PSD1 and PSM1:
PSD1:
#{
# Script module or binary module file associated with this manifest.
RootModule = 'Module1.psm1'
# Version number of this module.
# NOTE keep version in sync with Module1.nuspec
ModuleVersion = '2.7.2'
# ID used to uniquely identify this module
GUID = '24EC97AC-42B1-4719-9EF2-F49C14F8D148'
# Author of this module
Author = 'Me'
# Company or vendor of this module
CompanyName = 'Me'
# Description of the functionality provided by this module
Description = 'Example module for Stack post'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.1'
# Modules that must be imported into the global environment prior to importing this module
RequiredModules = #()
# Assemblies that must be loaded prior to importing this module
RequiredAssemblies = #('System.Web')
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = #('Deploy-MyResource')
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = #()
# Variables to export from this module
VariablesToExport = #()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = #()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = #{
PSData = #{
#region Unused PSData
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = #()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
#endregion Unused PSData
} # End of PSData hashtable
} # End of PrivateData hashtable
#region Unused Props
# Copyright statement for this module
# Copyright = '(c) 2019 appliedis.com. All rights reserved.'
# Supported PSEditions
# CompatiblePSEditions = #()
# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''
# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''
# Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# DotNetFrameworkVersion = ''
# Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only.
# CLRVersion = ''
# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''
# Script files (.ps1) that are run in the caller's environment prior to importing this module.
# ScriptsToProcess = #()
# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = #()
# Format files (.ps1xml) to be loaded when importing this module
# FormatsToProcess = 'Module1.Format.ps1xml'
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = #()
# DSC resources to export from this module
# DscResourcesToExport = #()
# List of all modules packaged with this module
# ModuleList = #()
# List of all files packaged with this module
# FileList = #()
# HelpInfo URI of this module
# HelpInfoURI = ''
# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''
#endregion Unused Props
}
PSM1:
# Get public and private function definition files.
$Private = #( Get-ChildItem -Path $PSScriptRoot\Function\private\*.ps1 -Recurse -ErrorAction SilentlyContinue )
$Public = #( Get-ChildItem -Path $PSScriptRoot\Functions\public\*.ps1 -Recurse -ErrorAction SilentlyContinue )
# Dot source the files
Foreach($import in #($Private + $Public))
{
Try
{
Set-StrictMode -Version Latest
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
# Export the public functions
Foreach($import in $Public)
{
Try
{
Export-ModuleMember -Function $import.BaseName
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
This is what I get when I try to use the module:
Use-Module (outside of the Module1 folder):
Using module .\Module1
$class1 = [Class1]::new()
Deploy-MyResource -EnumParam ValueOne -ClassParam $class1
What am I missing? All of the examples about modules and classes I'm finding are all in a single file.
I've created a repo on GitHub with the code here: https://github.com/SPSamL/PowerShell-Module-With-Classes
There's no good solution to this in pure PowerShell right now. If you need class definitions shared across multiple scripts and other modules, then you'll probably want to build a C# class library and include the DLL with one of your PowerShell module projects.
Whichever classes you would like to use across multiple functions or modules, put inside a single module file.
Afterward, you can utilize the using command: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_using?view=powershell-7 to import the namespace.
To load classes from a PowerShell module:
using module <module-name>
I use pre-v5 syntax (works with Core), which creates the enum into a global scope.
Add-Type -TypeDefinition #"
public enum Enum1 {
ValueOne,
ValueTwo
}
"#
Related
If I try to save a file in ISE, the default extension is Powershell Files with parenthesis (*.ps1, *.psm1, *.psd1, *.ps1xml, *.pssc, *.psrc, *.cdxml) and with descriptions
Powershell Scripts (*.ps1)
Powershell Modules (*.psm1)
Powershell Data Files (*.psd1)
Powershell Session Configuration Files (*.pssc)
Powershell Role Capability Files (*.psrc)
Powershell Xml Files (*.psxml, *.cdxml)
What do each of these do?
.ps1 files are PowerShell scripts; it is the most common type of PowerShell file and one that is the most like other shell scripts like .sh, .bat files
.psm1 files are PowerShell modules; these can contain multiple function scripts interacted as a package, can import modules into PowerShell with the Import-Module cmdlet as well as Install-Module
Other Module-Interaction cmdlets:
Export-ModuleMember
Find-Module
Get-InstalledModule
Get-Module
ImportSystemModules
New-Module
New-ModuleManifest
etc, tab through *Module* in PowerShell
.psd1 files are PowerShell data files or PowerShell module manifest files; they provide information and instructions and serve as manifest files for psm1 files/PowerShell modules; stores configuration data. Can serve as help file for PowerShell modules. For more information, see https://learn.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest?view=powershell-7. Manipulated with Import-PowerShellDataFile, New-ModuleManifest, Test-ModuleManifest, Update-ModuleManifest, and Import-LocalizedData cmdlets.
.ps1xml files are PowerShell XMLs for PowerShell modules, similar to .psd1 files but in XML format. A module manifest is also required for modules that support the Updatable Help feature. Updatable Help uses the HelpInfoUri key in the module manifest to find the Help information (HelpInfo XML) file that contains the location of the updated help files for the module. PowerShell XML files can serve as the help file for a module: Requires the existence of a module manifest file to do this. There are certain names for ps1xml files that are designated to do certain things, see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_types.ps1xml?view=powershell-7 and https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_format.ps1xml?view=powershell-7
.pssc files are PowerShell session configuration files; Describes session configuration files, which are used in a session configuration (also known as an "endpoint") to define the environment of sessions that use the session configuration. Session configuration files make it easy to create custom session configurations without using complex C# assemblies or scripts. .pssc files are in hashtable format and contain the configuration information for a PowerShell session. See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_session_configuration_files?view=powershell-7. .pssc files use the Register-PSSessionConfiguration, Set-PSSessionConfiguration, and New-PSSessionConfigurationFile cmdlets.
.psrc files are PowerShell role capability files; they define a set of capabilities that are used in session configuration and session configuration (pssc) files. Uses hashtable format. Uses the New-PSRoleCapabilityFile cmdlet. See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/new-psrolecapabilityfile?view=powershell-7
.cdxml files are PowerShell XML files that create cmdlets using CIM tools. See https://learn.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/cdxml-overview
Examples:
Example psd1:
#{
RootModule = 'ExampleModule.psm1'
ModuleVersion = '0.0.1'
GUID = '1234abcd-1234-abcd-xxxx-000000000000'
Author = 'User01'
CompanyName = 'Unknown'
Copyright = '(c) User01. All rights reserved.'
Description = 'Example module'
Example types.ps1xml:
<Type>
<Name>System.Object[]</Name>
<Members>
<AliasProperty>
<Name>Test</Name>
<ReferencedMemberName>
Length
</ReferencedMemberName>
</AliasProperty>
</Members>
</Type>
Adds new member Count to System.Arrays with member type AliasProperty:
gm -InputObject #(1,2,3,4)| ?{$_.Name -eq "test"}
TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
Test AliasProperty Test = Length
Example psrc:
New-PSRoleCapabilityFile `
-Path ".\Test.psrc" `
-Author "TestUser" `
-CompanyName "Test Company" `
-Description "Description" `
-ModulesToImport "Microsoft.PowerShell.Core"
Creates Test.psrc with parameters
Test.psrc content:
#{
# ID used to uniquely identify this document
GUID = '4fb9608e-474c-44c3-a752-ea1d9b27dcb0'
# Author of this document
Author = 'TestUser'
# Description of the functionality provided by these settings
Description = 'Description'
# Company associated with this document
CompanyName = 'Test Company'
# Copyright statement for this document
Copyright = '(c) 2020 TestUser. All rights reserved.'
# Modules to import when applied to a session
ModulesToImport = 'Microsoft.PowerShell.Core'
# Aliases to make visible when applied to a session
# VisibleAliases = 'Item1', 'Item2'
# Cmdlets to make visible when applied to a session
# VisibleCmdlets = 'Invoke-Cmdlet1', #{ Name = 'Invoke-Cmdlet2'; Parameters = #{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, #{ Name = 'Parameter2'; ValidatePattern = 'L*' } }
# Functions to make visible when applied to a session
# VisibleFunctions = 'Invoke-Function1', #{ Name = 'Invoke-Function2'; Parameters = #{ Name = 'Parameter1'; ValidateSet = 'Item1', 'Item2' }, #{ Name = 'Parameter2'; ValidatePattern = 'L*' } }
# External commands (scripts and applications) to make visible when applied to a session
# VisibleExternalCommands = 'Item1', 'Item2'
# Providers to make visible when applied to a session
# VisibleProviders = 'Item1', 'Item2'
# Scripts to run when applied to a session
# ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1'
# Aliases to be defined when applied to a session
# AliasDefinitions = #{ Name = 'Alias1'; Value = 'Invoke-Alias1'}, #{ Name = 'Alias2'; Value = 'Invoke-Alias2'}
# Functions to define when applied to a session
# FunctionDefinitions = #{ Name = 'MyFunction'; ScriptBlock = { param($MyInput) $MyInput } }
# Variables to define when applied to a session
# VariableDefinitions = #{ Name = 'Variable1'; Value = { 'Dynamic' + 'InitialValue' } }, #{ Name = 'Variable2'; Value = 'StaticInitialValue' }
# Environment variables to define when applied to a session
# EnvironmentVariables = #{ Variable1 = 'Value1'; Variable2 = 'Value2' }
# Format files (.ps1xml) to load when applied to a session
# FormatsToProcess = 'C:\ConfigData\MyFormats.ps1xml', 'C:\ConfigData\OtherFormats.ps1xml'
# Assemblies to load when applied to a session
# AssembliesToLoad = 'System.Web', 'System.OtherAssembly, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
}
Example pssc
New-PSSessionConfigurationFile -RoleDefinitions
#{ 'User' = #{ RoleCapabilities = 'Maintenance' }}
-Path .\Maintenance.pssc
Creates Maintenance.pssc with content
#{
# Version number of the schema used for this document
SchemaVersion = '2.0.0.0'
# ID used to uniquely identify this document
GUID = 'a4f7e8eb-f787-4f2c-bf0f-555db2ca2b05'
# Author of this document
Author = 'User'
# Description of the functionality provided by these settings
# Description = ''
# Session type defaults to apply for this session configuration. Can be 'RestrictedRemoteServer' (recommended), 'Empty', or 'Default'
SessionType = 'Default'
# Directory to place session transcripts for this session configuration
# TranscriptDirectory = 'C:\Transcripts\'
# Whether to run this session configuration as the machine's (virtual) administrator account
# RunAsVirtualAccount = $true
# Scripts to run when applied to a session
# ScriptsToProcess = 'C:\ConfigData\InitScript1.ps1', 'C:\ConfigData\InitScript2.ps1'
# User roles (security groups), and the role capabilities that should be applied to them when applied to a session
RoleDefinitions = #{
'User' = #{
'RoleCapabilities' = 'Maintenance' } }
}
Register file with
Register-PSSessionConfiguration -Name Name -Path .\Maintenance.pssc
Now PowerShell sessions started with Name use Maintenance.pssc. To start a PowerShell session with Name use
New-PSSession -ConfigurationName Name
Is there a way to find out using Powershell which running services are non-native to Windows ? In other words, services that aren't part Windows OS and are non-Microsoft. I want to find out all the services that were installed by our vendors.
Finding out running services is easy:
Get-Service | Where-Object {$_.Status -eq "Running"}
You can't do with it Get-Service alone, because even though the service-info objects it outputs have a .BinaryPathName property, that property is available in PowerShell Core only and it is not necessarily the true service binary, which is often a DLL hosted by the generic svchost.exe service host.
To find the binary file path in all cases, you must (also) query the service definitions in the registry. Once you have the file path, you can use Get-Item and the .VersionInfo property of the file-info objects returned to extract information such as the product and company name.
The Get-ServiceFileInfo function at the bottom does just that; it allows you to run commands such as:
# Return information about all services that aren't part of Windows.
# (May still include Microsoft services).
Get-ServiceFileInfo | Where ProductName -ne 'Microsoft® Windows® Operating System'
Get-ServiceFileInfo source code (PSv5+, but could be adapted to work with lower versions):
# Note: While it is possible to run without elevated privileges,
# not all file information is retrievable then.
#requires -runAsAdministrator
function Get-ServiceFileInfo {
Set-StrictMode -Version 1
Get-Service | ForEach-Object {
# PowerShell Core only:
# Get the service binary path, which may or may not be the true service
# executable.
$binaryPath = $_.BinaryPathName
# Windows PowerShell:
# We fall back on trying to obtain the "ImagePath" value from the registry.
# Note: Even in PowerShell Core there appear to be services that Get-Service fails
# to query even when running as admin, such as "SshdBroker"
# (a non-terminating error is issued).
# Reading from the registry is needed in that case too,
# but, only succeeds when running as admin.
if (-not $binaryPath) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$($_.Name)" ImagePath } catch { }
}
# Test for svchost.exe, which indicates the need to look for the service-specific DLL path elsewhere.
if ($binaryPath -like '*\svchost.exe *') {
# Get the actual binary (DLL) from the registry, subkey "Parameters", value "ServiceDLL"
# NOTE: Some services exist in *2* (multiple?) incarnations, as "<name>"" and "<name>_<num>"
# Only the "<name>" incarnation has the "ServiceDLL" value, so we fall back on that.
foreach ($keyName in $_.Name, ($_.Name -split '_')[0]) {
# NOTE: Most DLL-based services store the "ServiceDLL" value in the "Parameters" subkey, but
# some have it in the service's root key itself.
foreach ($subKeyName in "$keyName\Parameters", $keyName) {
$binaryPath = try { Get-ItemPropertyValue -EA Ignore "HKLM:\SYSTEM\CurrentControlSet\Services\$subKeyName" ServiceDLL } catch { }
if ($binaryPath) { break }
}
}
}
# Sanitize the path:
# * Some values have enclosing "...", so we strip them,
# * others have arguments, so we only take the first token.
$binaryPath = if ($binaryPath.StartsWith('"')) {
($binaryPath -split '"')[1]
} else {
# The path / command line isn't or doesn't start with a double-quoted token, which
# can mean one of two things:
# * It is a command line based on an unquoted executable, possibly with arguments.
# * It is a service DLL path - possibly with spaces in the (expanded) path.
if (Test-Path -LiteralPath $binaryPath -Type Leaf) {
$binaryPath # Value as a whole is a file path
} else {
(-split $binaryPath)[0] # Value is a command line, extract executable
}
}
$FileVersionInfo = if ($binaryPath) { (Get-Item -LiteralPath $binaryPath).VersionInfo }
# Construct the output object.
[pscustomobject] #{
Name = $_.Name
BinaryPath = if ($binaryPath) { $binaryPath } else { '(n/a)'; Write-Error "Failed to determine binary path for service '$($_.Name)'. Try running as admin." }
ProductName = $FileVersionInfo.ProductName
FileDescription = $FileVersionInfo.FileDescription
CompanyName = $FileVersionInfo.CompanyName
}
}
}
When I import my module I can't access the exposed members.
Placed my module in C:\Program Files\WindowsPowerShell\Modules.
When I import my module in powershell by:
Import-Module StuiterModule -Verbose
and then enter Invoke-Reboot it gives the following error:
Invoke-Reboot : The term 'Invoke-Reboot' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, 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:1
+ Invoke-Reboot
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Invoke-Reboot:String [], CommandNotFoundExeption
+ FullyQualifiedErrorId : CommandNotFoundException
Does anyone have an idea what I'm doing wrong?
Update
When I put -Force behind the Import Module everything works. Why is that and how can I fix this?
Code:
StuiterModule.psm1
$Public = #( Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" )
$Private = #( Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" )
#($Public + $Private) | ForEach-Object {
Try {
. $_.FullName
} Catch {
Write-Error -Message "Failed to import function $($_.FullName): $_"
}
}
Export-ModuleMember -Function $Public.BaseName
StuiterModule.psd1
#
# Module manifest for module 'StuiterModule'
#
# Generated by: StuiterSlurf
#
# Generated on: 29-5-2018
#
#{
# Script module or binary module file associated with this manifest.
RootModule = 'StuiterModule.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# ID used to uniquely identify this module
GUID = '0254592e-b712-4d70-844c-6e38cec20ee5'
# Author of this module
Author = 'StuiterSlurf'
# Copyright statement for this module
Copyright = '(c) 2018 StuiterSlurf. All rights reserved.'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Invoke-Reboot', 'Start-Program', 'Update-Program'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = #()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = #()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = #{
PSData = #{}
}
}
Public/Invoke-Reboot.ps1
# Reboot system
Function Invoke-Reboot {
[cmdletbinding()]
Param()
Write-Verbose "Starting reboot"
Restart-Computer
}
We are building a bigger project with PowerShell (a collection of scripts/functions that helps us setup some SharePoint environments/tenants).
A lot of the functions should reuse settings that are stored in a single, central place.
I couldn't find a "best practice" of how such a settings file/location should be best created and structured.
My idea is to store global settings in a separate file (a module file), for example Settings.psm1 with content like this:
# Set vars
$global:scriptEnvironment = "SP2016HOSTINGDEV"
$global:logFileName = "z_Migration_to_SP2016.log"
$global:languageMapping = #{
"en-US" = 1;
"de-DE" = 2;
}
$global:oldWsps = #(
[WspFile]#{ Filename = "comapany.solution.wsp"; IsDeployable = $true; IsGloballyDeployable = $false; FullTrustBinDeployment = $false },
[WspFile]#{ Filename = "company.solution2.server.wsp"; IsDeployable = $true; IsGloballyDeployable = $false; FullTrustBinDeployment = $false }
)
...
And in the other modules/scripts I then could always include those settings like this:
# Set vars
$scriptDirectory = Split-Path -parent $PSCommandPath
# Module import
Import-Module (Join-Path $scriptDirectory Settings.psm1) -Force -ErrorAction Stop
Import-Module (Join-Path $scriptDirectory Functions.psm1) -Force -ErrorAction Stop
# Functions
...
So this would make me able to use the global settings like this in the functions inside other script files:
Function WriteLog
{
param
(
[System.String]$LogString = "",
[System.String]$LogLevel = ""
)
WriteLogToPath -LogFileName $global:logFileName -LogString $LogString -LogLevel $LogLevel
}
Is this a good approach? Or shouldn't I use module files for this and if not what kind of files should I use instead?
I would probably collect all your scripts/functions in a module and use the registry to store the global settings. Read the values from the registry when the module is loaded, and have variables with default values for each setting in your module so that you can write missing values to the registry.
Something like this:
$modulename = Split-Path $PSScriptRoot -Leaf
$default_foo = 'something'
$default_bar = 'or other'
...
function Get-RegistrySetting($name) {
$path = "HKCU:\Software\${script:modulename}"
if (-not (Test-Path -LiteralPath $path)) {
New-Item -Path $path -Force | Out-Null
}
try {
Get-ItemProperty $path -Name $name -ErrorAction Stop |
Select-Object -Expand $name
} catch {
$val = Get-Variable -Scope script -Name "default_${name}" -ValueOnly -ErrorAction Stop
Set-ItemProperty $path -Name $name -Value $val
$val
}
}
$global:foo = Get-RegistrySetting 'foo'
$global:bar = Get-RegistrySetting 'bar'
...
For variables you only use inside your module you may want to use the script scope instead of the global scope.
I would avoid using the registry personally. I agree about using modules though. My approach would be to use a manifest module (i.e. using a .psd1 file which essentially is a key-value hashtable containing metadata about a module), and specifying a 'root' module with the RootModule key.
The module scope is now set by this RootModule, and you can define your variables in there.
You could separate out your functions into 'nested' modules (another manifest file key), and these are loaded automatically by PowerShell into the rootmodule scope, so should have access to those variables.
You can even control what variables and functions are exported using keys in that manifest file as well.
Check the Get-Configuration Powershell module. Concept is easy, module adds Environment variable in which (in JSON) definition of type and source is saved.
(dir env:PSGetConfiguration).Value
{
"Mode": "Xml",
"XmlPath": "C:\\Managers\\PSGetConfiguration.xml"
}
Configuration file is very simple and contains just key and value items
cat $(Get-ConfigurationSource).XmlPath
<Configuration>
<conf key="ExampleKey" value="ExampleValue" />
<conf key="key" value="Value123" />
<conf key="remove" value="remove" />
</Configuration>
Module expose two main functions Get-Configuration and Set-Configuration
Set-Configuration -Key k1 -Value v1
Get-Configuration -Key k1
v1
At start module saves XML in module directory it can be changed by manually changing the environment variable or using command Set-XmlConfigurationSource
SQL Configuration
By default module uses XML to store data, but second option is to store data in the SQL. Setting configuration is pretty easy:
Set-SqlConfigurationSource -SqlServerInstance ".\sql2017" -SqlServerDatabase "ConfigDatabase" -SqlServerSchema "config" -SqlServerTable "Configuration"
After this our config will be stored in SQL table.
Code is also available in github.
I am trying to test some custom DSC resources written as a class with the help of pester. However I am struggling to work out how to make the class available to a different file. This is not a pester issue really, I can not do this in powershell either. I have a module with a class in it much like the following in a SxDeployment.psm1 file
[DscResource()]
class SxWindowsService {
[void]Set(){
}
[bool]Test(){
return $true;
}
[SxWindowsService]Get(){
return $this
}
}
This module has a corresponding .psd1, which is listing the SxWindowsService class as a 'DscResourcesToExport'. It is being registered as a module, I can see this module when I do a Get-Module -ListAvailable and can also perform an Import-Module upon it.
I suppose my question really is how can I create a a reference to this class from another file?
Say I have a different file test.ps1 with the following
Import-Module SxDeployment
$class = [SxWindowsService]::new()
I get the following error, "Unable to find type [SxWindowsService]."
UPDATE
After a little more tinkering, I can create an instance of the class by changing the .psm1 file to a .ps file and changing the import-module statement for . .\SxDeployment.ps1. So it seems the problem is how do you consume resources defined in a DSC resource module file outside of a DSC configuration?
The type literal [SxWindowsService] won't be accessible outside the module. This is by design. Here is a quote from the release notes:
Class keyword >>> Defines a new class. This is a true .NET Framework type.
Class members are public, but only public within the module scope. You can't refer to the type name as a string (for example, New-Object doesn't work), and in this release, you can't use a type literal (for example, [MyClass]) outside the script/module file in which the class is defined.
Edit:
Well after having said the above. Since there is a bit of a mismatch between object activation in .NET and PowerShell then I thought to myself, there might just be a way. And I found it:
Import-Module C:\ps\StackOverflow\32743812\issue.psm1 -Force
function New-PSClassInstance {
param(
[Parameter(Mandatory)]
[String]$TypeName,
[object[]]$ArgumentList = $null
)
$ts = [System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object Location -eq $null |
Foreach-Object {
$_.Gettypes()
} | Where-Object name -eq $TypeName |
Select-Object -Last 1
if($ts) {
[System.Activator]::CreateInstance($ts,$ArgumentList )
} else {
$typeException = New-Object TypeLoadException $TypeName
$typeException.Data.Add("ArgumentList",$ArgumentList)
throw $typeException
}
}
New-PSClassinstance -TypeName SxWindowsService