Dark mode

Dark mode

There are 0 results matching

article card image dark article card image light

Published by · Jun 18, 2024 tools · 2 mins read

Introducing: Configuration Manager Set Implicit Uninstall Flag Tool

Setting Configuration Manager ConfigMgr Implicit Uninstall Flag with PowerShell for Required Application Deployments ...

See More
article card image dark article card image light

Published by · Jun 11, 2024 configmgr · 2 mins read

Configuration Manager Next Maintenance Window SQL Function

Get Next Configuration Manager Maintenance Window from a Schedule Token with Offset Days using an SQL Function. ...

See More
article card image dark article card image light

Published by · Jun 3, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 3

Add, Remove, or Replace Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 28, 2024 tools · 2 mins read

Introducing: Windows User Rights Assignment Tool - Part 2

Get and Report Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · May 22, 2024 tools · 1 mins read

Introducing: Windows User Rights Assignment Tool - Part 1

Get Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune Linux Onboarding Tool

Onboard Ubuntu Linux devices to Microsoft Intune using a bash script. Installs prerequisites and starts the user-driven enrollment. ...

See More
article card image dark article card image light

Published by · Apr 11, 2024 tools · 2 mins read

Introducing: Intune macOS Onboarding Tool

Onboard macOS devices to Microsoft Intune using a bash script that initiates the process. Optionally, the script converts mobile accounts, resets the FileVault key, and removes ...

See More
article card image dark article card image light

Published by · Jan 23, 2024 tools · 3 mins read

Introducing: Intune Device Renaming Tool

Rename Intune Devices by setting a Prefix or using a User Attribute as Prefix. Supports Windows, macOS, and Linux ...

See More
article card image dark article card image light

Published by · Dec 8, 2023 intune · 5 mins read

Intune Logs: A Deep Dive into Locations, Interpretation, and Configuration

A Comprehensive Guide to Locations, Interpretation, and Configuration of Intune Logs ...

See More
article card image dark article card image light

Published by · Aug 14, 2023 configmgr · 2 mins read

Configuration Manager Console Extension to show Device Collection Membership with Console Builder

Use the Configuration Manager Console Builder, to add Collection Membership View to the Device Node ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 3 mins read

Introducing: Configuration Manager SSRS Dashboards

A Configuration Manager Dashboards solution with Reports for Software Updates, Bitlocker and more ...

See More
article card image dark article card image light

Published by · Aug 3, 2023 tools · 2 mins read

Introducing: PowerShell WMI Management Toolkit Module

Streamline your WMI Namespace, Class, and Instance Management with our PowerShell Module ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 1 mins read

Configuration Manager detailed, filterable Port Documentation

Configuration Manager detailed, filterable port documentation as an excel document ...

See More
article card image dark article card image light

Published by · Jul 14, 2023 configmgr · 3 mins read

Configuration Manager PXE TFTP Window Size Bug

Configuration Manager TFTP Block Size and TFTP Window Size Correct Configuration ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 4 mins read

Introducing: Configuration Manager Client Cache Cleanup Tool

Cleaning the Configuration Manager Client Cache the Right Way with PowerShell and Configuration Baselines ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More
article card image dark article card image light

Published by · Jun 17, 2023 tools · 1 mins read

Introducing: Windows Update Database Reinitialization Tool

Proactively repair corrupted Windows Update Database with Powershell and Configuration Manager ...

See More
article card image dark article card image light

Published by · Mar 31, 2023 tools · 3 mins read

Introducing: Configuration Manager SQL Products Reporting

A Complete SQL Products reporting solution using Configuration Manager ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 configmgr · 1 mins read

Application Detection Method using the Configuration Manager Application Version

Replace hardcoded application version in scripts, with the Configuration Manager Application Version ...

See More
article card image dark article card image light

Published by · Jan 28, 2023 tools · 3 mins read

Introducing: Certificate Management Toolkit

Managing Certificates with Configuration Manager and PowerShell by using just the Public Key ...

See More
article card image dark article card image light

Published by · Jan 7, 2019 reports · 2 mins read

Configuration Manager Device Boundary and Network Information Report

List Device Boundaries and Network Information with Configuration Manager ...

See More
article card image dark article card image light

Published by · Sep 9, 1980 help · 5 mins read

MEM.Zone Blog Publishing Documentation

Publishing Documentation for MEM.Zone ...

See More

We couldn’t find anything related to

“SCCM”

BLOG / tools zone

Introducing: Windows User Rights Assignment Tool - Part 2

Published by Popovici Ioan · May 28, 2024 · 2 mins read
article card image dark article card image light

Quick Summary

Managing User Rights Assignment is not always straight forward especially with Intune. To address this issue we have created a PowerShell tool to help you manage User Rights Assignment on Windows devices. This will be a three part series where we will cover getting, setting and writing User Rights Assignment to WMI for easy reporting.

Part 2 covers writing the User Rights Assignments to WMI for easy reporting.

This tool can be used as a standalone script or integrated into your Configuration Manager or Intune Compliance Baseline or Script.

Prerequisites


Recommendations

  • Always use a test environment to validate your configuration.

Parameters

Principal

Defines the Principal to get the rights for.

Privilege

Defines the User Right(s) to get the principals for.

Notes

If you use the Principal Name instead of a SID you need to localize your Principal Name with the locale of the OS this script will be running on.

Notes

The result will be added to the Win32_UserRightsAssignment WMI class.


Examples

Principal Name

Get-UserRightsAssignmentWmi.ps1 -Principal 'CONTOSO\Group'

Principal SID

Get-UserRightsAssignmentWmi.ps1 -Principal '*S-1-5-19'

Privilege Type

Get-UserRightsAssignmentWmi.ps1 -Privilege 'SeServiceLogonRight', 'SeRemoteInteractiveLogonRight'

Preview

article card image powershell-set-windows-user-rights-assignment.gif
Get User Rights Assignment WMI

Code

   1<#
   2.SYNOPSIS
   3    Gets user rights assignment for a local computer.
   4.DESCRIPTION
   5    Gets user rights assignment for a local computer, and adds the result to the `Win32_UserRightsAssignment` WMI class.
   6.PARAMETER Principal
   7    Defines the Principal to get the rights for.
   8    If you use the Principal Name instead of a SID you need to localize your Principal Names with the locale of the OS this script will be running on.
   9    Default is: '*'. Supports wildcards.
  10.PARAMETER Privilege
  11    Defines the User Right(s) to get the principals for.
  12    Valid values are:
  13        SeAssignPrimaryTokenPrivilege
  14        SeAuditPrivilege
  15        SeBackupPrivilege
  16        SeBatchLogonRight
  17        SeChangeNotifyPrivilege
  18        SeCreateGlobalPrivilege
  19        SeCreatePagefilePrivilege
  20        SeCreatePermanentPrivilege
  21        SeCreateSymbolicLinkPrivilege
  22        SeCreateTokenPrivilege
  23        SeDebugPrivilege
  24        SeDelegateSessionUserImpersonatePrivilege
  25        SeDenyBatchLogonRight
  26        SeDenyInteractiveLogonRight
  27        SeDenyNetworkLogonRight
  28        SeDenyRemoteInteractiveLogonRight
  29        SeDenyServiceLogonRight
  30        SeEnableDelegationPrivilege
  31        SeImpersonatePrivilege
  32        SeIncreaseBasePriorityPrivilege
  33        SeIncreaseQuotaPrivilege
  34        SeIncreaseWorkingSetPrivilege
  35        SeInteractiveLogonRight
  36        SeLoadDriverPrivilege
  37        SeLockMemoryPrivilege
  38        SeMachineAccountPrivilege
  39        SeManageVolumePrivilege
  40        SeNetworkLogonRight
  41        SeProfileSingleProcessPrivilege
  42        SeRelabelPrivilege
  43        SeRemoteInteractiveLogonRight
  44        SeRemoteShutdownPrivilege
  45        SeRestorePrivilege
  46        SeSecurityPrivilege
  47        SeServiceLogonRight
  48        SeShutdownPrivilege
  49        SeSyncAgentPrivilege
  50        SeSystemEnvironmentPrivilege
  51        SeSystemProfilePrivilege
  52        SeSystemtimePrivilege
  53        SeTakeOwnershipPrivilege
  54        SeTcbPrivilege
  55        SeTimeZonePrivilege
  56        SeTrustedCredManAccessPrivilege
  57        SeUndockPrivilege
  58.EXAMPLE
  59    Get-UserRightsAssignment.ps1
  60.EXAMPLE
  61    Get-UserRightsAssignment.ps1 -Principal 'CONTOSO\Group'
  62.EXAMPLE
  63    Get-UserRightsAssignment.ps1 -Principal '*S-1-5-19'
  64.EXAMPLE
  65    Get-UserRightsAssignment.ps1 -Privilege 'SeServiceLogonRight', 'SeRemoteInteractiveLogonRight'
  66.INPUTS
  67    None.
  68.OUTPUTS
  69    System.Object
  70    System.Exception
  71.NOTES
  72    Created by Ioan Popovici
  73.LINK
  74    https://MEMZ.one/Get-UserRightsAssignmentWmi
  75.LINK
  76    https://MEMZ.one/Get-UserRightsAssignmentWmi-CHANGELOG
  77.LINK
  78    https://MEMZ.one/Get-UserRightsAssignmentWmi-GIT
  79.LINK
  80    https://MEM.Zone/ISSUES
  81.COMPONENT
  82    User Rights Assignment
  83.FUNCTIONALITY
  84    Gets User Rights Assignment.
  85#>
  86
  87<#
  88[CmdletBinding(DefaultParameterSetName = 'Principal')]
  89Param (
  90    [Parameter(Mandatory = $false, ParameterSetName = 'Principal', Position = 0)]
  91    [SupportsWildcards()]
  92    [ValidateNotNullorEmpty()]
  93    [Alias('PrincipalName')]
  94    [string]$Principal = '*',
  95    [Parameter(Mandatory = $true, ParameterSetName = 'Privileges', Position = 1)]
  96    [ValidateSet('SeAssignPrimaryTokenPrivilege', 'SeAuditPrivilege', 'SeBackupPrivilege', 'SeBatchLogonRight', 'SeChangeNotifyPrivilege',
  97        'SeCreateGlobalPrivilege', 'SeCreatePagefilePrivilege', 'SeCreatePermanentPrivilege', 'SeCreateSymbolicLinkPrivilege', 'SeCreateTokenPrivilege',
  98        'SeDebugPrivilege', 'SeDelegateSessionUserImpersonatePrivilege', 'SeDenyBatchLogonRight', 'SeDenyInteractiveLogonRight', 'SeDenyNetworkLogonRight',
  99        'SeDenyRemoteInteractiveLogonRight', 'SeDenyServiceLogonRight', 'SeEnableDelegationPrivilege', 'SeImpersonatePrivilege', 'SeIncreaseBasePriorityPrivilege',
 100        'SeIncreaseQuotaPrivilege', 'SeIncreaseWorkingSetPrivilege', 'SeInteractiveLogonRight', 'SeLoadDriverPrivilege', 'SeLockMemoryPrivilege', 'SeMachineAccountPrivilege',
 101        'SeManageVolumePrivilege', 'SeNetworkLogonRight', 'SeProfileSingleProcessPrivilege', 'SeRelabelPrivilege', 'SeRemoteInteractiveLogonRight', 'SeRemoteShutdownPrivilege',
 102        'SeRestorePrivilege', 'SeSecurityPrivilege', 'SeServiceLogonRight', 'SeShutdownPrivilege', 'SeSyncAgentPrivilege', 'SeSystemEnvironmentPrivilege', 'SeSystemProfilePrivilege',
 103        'SeSystemtimePrivilege', 'SeTakeOwnershipPrivilege', 'SeTcbPrivilege', 'SeTimeZonePrivilege', 'SeTrustedCredManAccessPrivilege', 'SeUndockPrivilege', IgnoreCase = $true
 104    )]
 105    [Alias('Rights')]
 106    [string[]]$Privilege
 107)
 108#>
 109
 110##*=============================================
 111##* VARIABLE DECLARATION
 112##*=============================================
 113#region VariableDeclaration
 114
 115## Add the UserRightsFlags enum type .NET definition to the current PowerShell session
 116Add-Type @'
 117using System;
 118
 119[Flags]
 120public enum UserRightsFlags : ulong  {
 121    None                                      = 0,
 122    SeAssignPrimaryTokenPrivilege             = 1L << 0,
 123    SeAuditPrivilege                          = 1L << 1,
 124    SeBackupPrivilege                         = 1L << 2,
 125    SeBatchLogonRight                         = 1L << 3,
 126    SeChangeNotifyPrivilege                   = 1L << 4,
 127    SeCreateGlobalPrivilege                   = 1L << 5,
 128    SeCreatePagefilePrivilege                 = 1L << 6,
 129    SeCreatePermanentPrivilege                = 1L << 7,
 130    SeCreateSymbolicLinkPrivilege             = 1L << 8,
 131    SeCreateTokenPrivilege                    = 1L << 9,
 132    SeDebugPrivilege                          = 1L << 10,
 133    SeDelegateSessionUserImpersonatePrivilege = 1L << 11,
 134    SeDenyBatchLogonRight                     = 1L << 12,
 135    SeDenyInteractiveLogonRight               = 1L << 13,
 136    SeDenyNetworkLogonRight                   = 1L << 14,
 137    SeDenyRemoteInteractiveLogonRight         = 1L << 15,
 138    SeDenyServiceLogonRight                   = 1L << 16,
 139    SeEnableDelegationPrivilege               = 1L << 17,
 140    SeImpersonatePrivilege                    = 1L << 18,
 141    SeIncreaseBasePriorityPrivilege           = 1L << 19,
 142    SeIncreaseQuotaPrivilege                  = 1L << 20,
 143    SeIncreaseWorkingSetPrivilege             = 1L << 21,
 144    SeInteractiveLogonRight                   = 1L << 22,
 145    SeLoadDriverPrivilege                     = 1L << 23,
 146    SeLockMemoryPrivilege                     = 1L << 24,
 147    SeMachineAccountPrivilege                 = 1L << 25,
 148    SeManageVolumePrivilege                   = 1L << 26,
 149    SeNetworkLogonRight                       = 1L << 27,
 150    SeProfileSingleProcessPrivilege           = 1L << 28,
 151    SeRelabelPrivilege                        = 1L << 29,
 152    SeRemoteInteractiveLogonRight             = 1L << 30,
 153    SeRemoteShutdownPrivilege                 = 1L << 31,
 154    SeRestorePrivilege                        = 1L << 32,
 155    SeSecurityPrivilege                       = 1L << 33,
 156    SeServiceLogonRight                       = 1L << 34,
 157    SeShutdownPrivilege                       = 1L << 35,
 158    SeSyncAgentPrivilege                      = 1L << 36,
 159    SeSystemEnvironmentPrivilege              = 1L << 37,
 160    SeSystemProfilePrivilege                  = 1L << 38,
 161    SeSystemtimePrivilege                     = 1L << 39,
 162    SeTakeOwnershipPrivilege                  = 1L << 40,
 163    SeTcbPrivilege                            = 1L << 41,
 164    SeTimeZonePrivilege                       = 1L << 42,
 165    SeTrustedCredManAccessPrivilege           = 1L << 43,
 166    SeUndockPrivilege                         = 1L << 44
 167}
 168'@
 169
 170#endregion
 171##*=============================================
 172##* END VARIABLE DECLARATION
 173##*=============================================
 174
 175##*=============================================
 176##* FUNCTION LISTINGS
 177##*=============================================
 178#region FunctionListings
 179
 180#region Function Resolve-Principal
 181Function Resolve-Principal {
 182    <#
 183.SYNOPSIS
 184    Resolves a Principal or Principals.
 185.DESCRIPTION
 186    Resolves a Principal or Principals to SID or Principal Name.
 187.PARAMETER Principal
 188    Specifies the Principal to resolve.
 189.EXAMPLE
 190    Resolve-Principal -Principal 'CONTOSO\User'
 191.EXAMPLE
 192    Resolve-Principal -Principal 'CONTOSO\User', 'CONTOSO\Group', 'BUILTIN\Administrators'
 193.EXAMPLE
 194    Resolve-Principal -Principal 'S-1-5-21-1234567890-1234567890-1234567890-500'
 195.EXAMPLE
 196    Resolve-Principal -Principal 'S-1-5-21-1234567890-1234567890-1234567890-500', 'S-1-5-21-1234567890-1234567890-1234567890-501'
 197.INPUTS
 198    System.Array
 199.OUTPUTS
 200    System.Object
 201    System.Exception
 202.NOTES
 203    Created by Ioan Popovici
 204.LINK
 205    https://MEM.Zone
 206.LINK
 207    https://MEM.Zone/GIT
 208.LINK
 209    https://MEM.Zone/ISSUES
 210.COMPONENT
 211    Security Principal
 212.FUNCTIONALITY
 213    Resolves a Principal or Principals to SID or Principal Name.
 214#>
 215    [CmdletBinding()]
 216    Param (
 217        [Parameter(Mandatory = $true, Position = 0)]
 218        [ValidateNotNullorEmpty()]
 219        [Alias('SecurityPrincipal')]
 220        [string[]]$Principal
 221    )
 222    Begin {
 223
 224        ## Set SID regex match Pattern
 225        [regex]$Pattern = 'S-\d-(?:\d+-){1,14}\d+'
 226
 227        ## Initialize output object
 228        $Output = $null
 229    }
 230    Process {
 231        Try {
 232
 233            ## Resolve Principal
 234            $Output = ForEach ($PrincipalItem in $Principal) {
 235                Try {
 236                    #  Set Principal type
 237                    [string]$SIDMatch = (Select-String -Pattern $Pattern -InputObject $PrincipalItem).Matches.Value
 238                    [string]$PrincipalType = If ([string]::IsNullOrEmpty($SIDMatch)) { 'PrincipalName' } Else { 'PrincipalSID' }
 239                    #  Resolve Principal
 240                    Switch ($PrincipalType) {
 241                        'PrincipalName' {
 242                            $NTAccountObject = New-Object System.Security.Principal.NTAccount($PrincipalItem)
 243                            $NTAccountObject.Translate([System.Security.Principal.SecurityIdentifier]).Value
 244                            Break
 245                        }
 246                        'PrincipalSID' {
 247                            $SIDObject = New-Object System.Security.Principal.SecurityIdentifier($PrincipalItem.Replace('*', ''))
 248                            $SIDObject.Translate([Security.Principal.NTAccount]).Value
 249                            Break
 250                        }
 251                    }
 252                }
 253                Catch {
 254
 255                    ## Return custom error. The error handling is done here in order not to break the ForEach loop and allow it to continue.
 256                    $Exception = [Exception]::new($PsItem.Exception.Message)
 257                    $ExceptionType = [Management.Automation.ErrorCategory]::ObjectNotFound
 258                    $ErrorRecord = [System.Management.Automation.ErrorRecord]::new($Exception, $PsItem.FullyQualifiedErrorId, $ExceptionType, $PrincipalItem)
 259                    $PSCmdlet.WriteError($ErrorRecord)
 260                }
 261            }
 262        }
 263        Catch {
 264            $PSCmdlet.WriteError($PSItem)
 265        }
 266        Finally {
 267            Write-Output -InputObject $Output
 268        }
 269    }
 270}
 271#endregion
 272
 273#region Function Get-UserRightsAssignment
 274Function Get-UserRightsAssignment {
 275    <#
 276.SYNOPSIS
 277    Gets user rights assignment.
 278.DESCRIPTION
 279    Gets user rights assignment for a local computer.
 280.PARAMETER Principal
 281    Defines the Principal to get the rights for.
 282    Default is: 'All'.
 283.PARAMETER Privilege
 284    Defines the User Right(s) to get the principals for.
 285    Valid values are:
 286        SeAssignPrimaryTokenPrivilege
 287        SeAuditPrivilege
 288        SeBackupPrivilege
 289        SeBatchLogonRight
 290        SeChangeNotifyPrivilege
 291        SeCreateGlobalPrivilege
 292        SeCreatePagefilePrivilege
 293        SeCreatePermanentPrivilege
 294        SeCreateSymbolicLinkPrivilege
 295        SeCreateTokenPrivilege
 296        SeDebugPrivilege
 297        SeDelegateSessionUserImpersonatePrivilege
 298        SeDenyBatchLogonRight
 299        SeDenyInteractiveLogonRight
 300        SeDenyNetworkLogonRight
 301        SeDenyRemoteInteractiveLogonRight
 302        SeDenyServiceLogonRight
 303        SeEnableDelegationPrivilege
 304        SeImpersonatePrivilege
 305        SeIncreaseBasePriorityPrivilege
 306        SeIncreaseQuotaPrivilege
 307        SeIncreaseWorkingSetPrivilege
 308        SeInteractiveLogonRight
 309        SeLoadDriverPrivilege
 310        SeLockMemoryPrivilege
 311        SeMachineAccountPrivilege
 312        SeManageVolumePrivilege
 313        SeNetworkLogonRight
 314        SeProfileSingleProcessPrivilege
 315        SeRelabelPrivilege
 316        SeRemoteInteractiveLogonRight
 317        SeRemoteShutdownPrivilege
 318        SeRestorePrivilege
 319        SeSecurityPrivilege
 320        SeServiceLogonRight
 321        SeShutdownPrivilege
 322        SeSyncAgentPrivilege
 323        SeSystemEnvironmentPrivilege
 324        SeSystemProfilePrivilege
 325        SeSystemtimePrivilege
 326        SeTakeOwnershipPrivilege
 327        SeTcbPrivilege
 328        SeTimeZonePrivilege
 329        SeTrustedCredManAccessPrivilege
 330        SeUndockPrivilege
 331.EXAMPLE
 332    Get-UserRightsAssignment
 333.EXAMPLE
 334    Get-UserRightsAssignment -Principal 'CONTOSO\Group'
 335.EXAMPLE
 336    Get-UserRightsAssignment -Principal '*S-1-5-19'
 337.EXAMPLE
 338    Get-UserRightsAssignment -Privilege 'SeServiceLogonRight', 'SeRemoteInteractiveLogonRight'
 339.INPUTS
 340    None.
 341.OUTPUTS
 342    System.Object
 343    System.Exception
 344.NOTES
 345    Created by Ioan Popovici
 346.LINK
 347    https://MEM.Zone
 348.LINK
 349    https://MEM.Zone/GIT
 350.LINK
 351    https://MEM.Zone/ISSUES
 352.COMPONENT
 353    User Rights Assignment
 354.FUNCTIONALITY
 355    Gets User Rights Assignment.
 356#>
 357    [CmdletBinding(DefaultParameterSetName = 'Principal')]
 358    Param (
 359        [Parameter(Mandatory = $false, ParameterSetName = 'Principal', Position = 0)]
 360        [SupportsWildcards()]
 361        [ValidateNotNullorEmpty()]
 362        [Alias('PrincipalName')]
 363        [string]$Principal = '*',
 364        [Parameter(Mandatory = $true, ParameterSetName = 'Privileges', Position = 1)]
 365        [ValidateSet('SeAssignPrimaryTokenPrivilege', 'SeAuditPrivilege', 'SeBackupPrivilege', 'SeBatchLogonRight', 'SeChangeNotifyPrivilege',
 366            'SeCreateGlobalPrivilege', 'SeCreatePagefilePrivilege', 'SeCreatePermanentPrivilege', 'SeCreateSymbolicLinkPrivilege', 'SeCreateTokenPrivilege',
 367            'SeDebugPrivilege', 'SeDelegateSessionUserImpersonatePrivilege', 'SeDenyBatchLogonRight', 'SeDenyInteractiveLogonRight', 'SeDenyNetworkLogonRight',
 368            'SeDenyRemoteInteractiveLogonRight', 'SeDenyServiceLogonRight', 'SeEnableDelegationPrivilege', 'SeImpersonatePrivilege', 'SeIncreaseBasePriorityPrivilege',
 369            'SeIncreaseQuotaPrivilege', 'SeIncreaseWorkingSetPrivilege', 'SeInteractiveLogonRight', 'SeLoadDriverPrivilege', 'SeLockMemoryPrivilege', 'SeMachineAccountPrivilege',
 370            'SeManageVolumePrivilege', 'SeNetworkLogonRight', 'SeProfileSingleProcessPrivilege', 'SeRelabelPrivilege', 'SeRemoteInteractiveLogonRight', 'SeRemoteShutdownPrivilege',
 371            'SeRestorePrivilege', 'SeSecurityPrivilege', 'SeServiceLogonRight', 'SeShutdownPrivilege', 'SeSyncAgentPrivilege', 'SeSystemEnvironmentPrivilege', 'SeSystemProfilePrivilege',
 372            'SeSystemtimePrivilege', 'SeTakeOwnershipPrivilege', 'SeTcbPrivilege', 'SeTimeZonePrivilege', 'SeTrustedCredManAccessPrivilege', 'SeUndockPrivilege', IgnoreCase = $true
 373        )]
 374        [Alias('Rights')]
 375        [string[]]$Privilege
 376    )
 377    Begin {
 378
 379        ## Set export file path
 380        [string]$TempFolderPath = [System.IO.Path]::GetTempPath()
 381        [string]$RandomFileName = [System.IO.Path]::GetRandomFileName()
 382        [string]$ExportFilePath = Join-Path -Path $TempFolderPath -ChildPath $RandomFileName
 383        [string]$System32Path = [Environment]::GetFolderPath([Environment+SpecialFolder]::System)
 384
 385        ## Set SID regex match Pattern
 386        [regex]$Pattern = 'S-\d-(?:\d+-){1,14}\d+'
 387
 388        ## Set output object
 389        $Output = $null
 390    }
 391    Process {
 392        Try {
 393
 394            ## Check for Admin Rights
 395            [boolean]$IsAdministrator = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
 396            If (-not $IsAdministrator) { Throw 'You must have administrative privileges to run this script!' }
 397
 398            ## Check if Principal is SID
 399            [string]$SIDMatch = (Select-String -Pattern $Pattern -InputObject $Principal).Matches.Value
 400            If (-not [string]::IsNullOrEmpty($SIDMatch)) { $Principal = Resolve-Principal -Principal $Principal -ErrorAction 'Stop' }
 401            Else { Write-Warning -Message 'You specified a Principal Name. This is not recommended if the names are not localized for the OS this script will be running on. Please use SID instead.' }
 402
 403            ## Set ScEdit.exe path
 404            [string]$SecEdit = Join-Path -Path $System32Path -ChildPath 'SecEdit.exe' -Resolve
 405
 406            ## Export User Rights Assignment to file using SecEdit.exe
 407            $null = & $SecEdit /export /cfg $ExportFilePath /areas USER_RIGHTS
 408
 409            ## Select User Rights Assignment from file
 410            [regex]$Pattern = '^(Se\S+) = (\S+)'
 411            $UserRightsMatches = (Select-String -Path $ExportFilePath -Pattern $Pattern)
 412
 413            ## Assemble Result object
 414            $Result = ForEach ($UserRightsMatch in $UserRightsMatches) {
 415                $SID = $UserRightsMatch.Matches[0].Groups[2].Value -split ','
 416                [pscustomobject]@{
 417                    Privilege     = $UserRightsMatch.Matches[0].Groups[1].Value
 418                    PrincipalSID  = $SID
 419                    PrincipalName = Resolve-Principal -Principal $SID
 420                }
 421            }
 422
 423            ## Filter Output object according to parameters
 424            If ($PSCmdlet.ParameterSetName -eq 'Principal') {
 425                If ($Principal -ne '*') {
 426                    $FilterResult = $Result.Where({ $PsItem.PrincipalName -like $Principal })
 427                    $Output = [pscustomobject]@{
 428                        #  Stop on unresolved SID, account should exist
 429                        PrincipalSID  = Resolve-Principal -Principal $Principal -ErrorAction 'Stop'
 430                        PrincipalName = $Principal
 431                        Privilege     = @($FilterResult.Privilege)
 432                    }
 433                }
 434                Else {
 435                    $UniquePrincipals = $Result.PrincipalName | Sort-Object -Unique
 436                    $Output = ForEach ($UniquePrincipal in $UniquePrincipals) {
 437                        $FilterResult = ($Result.Where({ $PsItem.PrincipalName -eq $UniquePrincipal }))
 438                        [pscustomobject]@{
 439                            #  Continue on unresolved SID, account might be deleted
 440                            PrincipalSID  = Resolve-Principal -Principal $UniquePrincipal -ErrorAction 'SilentlyContinue'
 441                            PrincipalName = $UniquePrincipal
 442                            Privilege     = @($FilterResult.Privilege)
 443                        }
 444                    }
 445                }
 446            }
 447            Else { $Output = $Result.Where({ $Privilege -contains $PsItem.Privilege }) }
 448        }
 449        Catch {
 450            $PSCmdlet.WriteError($PSItem)
 451        }
 452        Finally {
 453            Write-Output -InputObject $Output
 454        }
 455    }
 456    End {
 457        Remove-Item -Path $ExportFilePath -Force -ErrorAction 'SilentlyContinue'
 458    }
 459}
 460#endregion
 461
 462#region ConvertTo-HashtableFromPsCustomObject
 463Function ConvertTo-HashtableFromPsCustomObject {
 464    <#
 465.SYNOPSIS
 466    Converts a custom object to a hashtable.
 467.DESCRIPTION
 468    Converts a custom powershell object to a hashtable.
 469.PARAMETER PsCustomObject
 470    Specifies the custom object to be converted.
 471.EXAMPLE
 472    ConvertTo-HashtableFromPsCustomObject -PsCustomObject $PsCustomObject
 473.EXAMPLE
 474    $PsCustomObject | ConvertTo-HashtableFromPsCustomObject
 475.INPUTS
 476    System.Object
 477.OUTPUTS
 478    System.Object
 479.NOTES
 480    Created Ioan Popovici
 481.LINK
 482    https://MEM.Zone
 483.LINK
 484    https://MEM.Zone/ISSUES
 485.COMPONENT
 486    Conversion
 487.FUNCTIONALITY
 488    Convert custom object to hashtable
 489#>
 490    [CmdletBinding()]
 491    Param(
 492        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
 493        [pscustomobject]$PsCustomObject
 494    )
 495    Begin {
 496
 497        ## Preservers hashtable parameter order
 498        [System.Collections.Specialized.OrderedDictionary]$Output = @{}
 499    }
 500    Process {
 501
 502        ## The '.PsObject.Members' method preservers the order of the members, Get-Member does not.
 503        [object]$ObjectProperties = $PsCustomObject.PsObject.Members | Where-Object -Property 'MemberType' -EQ 'NoteProperty'
 504        ForEach ($Property in $ObjectProperties) { $Output.Add($Property.Name, $PsCustomObject.$($Property.Name)) }
 505        Write-Output -InputObject $Output
 506    }
 507}
 508#endregion
 509
 510#region PsWmiToolkit
 511##*=============================================
 512##* MODULE DEFINITION
 513##*=============================================
 514
 515#region Function Resolve-Error
 516Function Resolve-Error {
 517<#
 518.SYNOPSIS
 519    Enumerate error record details.
 520.DESCRIPTION
 521    Enumerate an error record, or a collection of error record, properties. By default, the details for the last error will be enumerated.
 522.PARAMETER ErrorRecord
 523    The error record to resolve. The default error record is the latest one: $global:Error[0]. This parameter will also accept an array of error records.
 524.PARAMETER Property
 525    The list of properties to display from the error record. Use "*" to display all properties.
 526    Default list of error properties is: Message, FullyQualifiedErrorId, ScriptStackTrace, PositionMessage, InnerException
 527.PARAMETER GetErrorRecord
 528    Get error record details as represented by $_.
 529.PARAMETER GetErrorInvocation
 530    Get error record invocation information as represented by $_.InvocationInfo.
 531.PARAMETER GetErrorException
 532    Get error record exception details as represented by $_.Exception.
 533.PARAMETER GetErrorInnerException
 534    Get error record inner exception details as represented by $_.Exception.InnerException. Will retrieve all inner exceptions if there is more than one.
 535.EXAMPLE
 536    Resolve-Error
 537.EXAMPLE
 538    Resolve-Error -Property *
 539.EXAMPLE
 540    Resolve-Error -Property InnerException
 541.EXAMPLE
 542    Resolve-Error -GetErrorInvocation:$false
 543.NOTES
 544.LINK
 545    https://psappdeploytoolkit.com
 546#>
 547    [CmdletBinding()]
 548    Param (
 549        [Parameter(Mandatory = $false, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
 550        [AllowEmptyCollection()]
 551        [array]$ErrorRecord,
 552        [Parameter(Mandatory = $false, Position = 1)]
 553        [ValidateNotNullorEmpty()]
 554        [string[]]$Property = ('Message', 'InnerException', 'FullyQualifiedErrorId', 'ScriptStackTrace', 'PositionMessage'),
 555        [Parameter(Mandatory = $false, Position = 2)]
 556        [switch]$GetErrorRecord = $true,
 557        [Parameter(Mandatory = $false, Position = 3)]
 558        [switch]$GetErrorInvocation = $true,
 559        [Parameter(Mandatory = $false, Position = 4)]
 560        [switch]$GetErrorException = $true,
 561        [Parameter(Mandatory = $false, Position = 5)]
 562        [switch]$GetErrorInnerException = $true
 563    )
 564
 565    Begin {
 566        ## If function was called without specifying an error record, then choose the latest error that occurred
 567        If (-not $ErrorRecord) {
 568            If ($global:Error.Count -eq 0) {
 569                #Write-Warning -Message "The `$Error collection is empty"
 570                Return
 571            }
 572            Else {
 573                [array]$ErrorRecord = $global:Error[0]
 574            }
 575        }
 576
 577        ## Allows selecting and filtering the properties on the error object if they exist
 578        [scriptblock]$SelectProperty = {
 579            Param (
 580                [Parameter(Mandatory = $true)]
 581                [ValidateNotNullorEmpty()]
 582                $InputObject,
 583                [Parameter(Mandatory = $true)]
 584                [ValidateNotNullorEmpty()]
 585                [string[]]$Property
 586            )
 587
 588            [string[]]$ObjectProperty = $InputObject | Get-Member -MemberType '*Property' | Select-Object -ExpandProperty 'Name'
 589            ForEach ($Prop in $Property) {
 590                If ($Prop -eq '*') {
 591                    [string[]]$PropertySelection = $ObjectProperty
 592                    Break
 593                }
 594                ElseIf ($ObjectProperty -contains $Prop) {
 595                    [string[]]$PropertySelection += $Prop
 596                }
 597            }
 598            Write-Output -InputObject $PropertySelection
 599        }
 600
 601        #  Initialize variables to avoid error if 'Set-StrictMode' is set
 602        $LogErrorRecordMsg = $null
 603        $LogErrorInvocationMsg = $null
 604        $LogErrorExceptionMsg = $null
 605        $LogErrorMessageTmp = $null
 606        $LogInnerMessage = $null
 607    }
 608    Process {
 609        If (-not $ErrorRecord) { Return }
 610        ForEach ($ErrRecord in $ErrorRecord) {
 611            ## Capture Error Record
 612            If ($GetErrorRecord) {
 613                [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord -Property $Property
 614                $LogErrorRecordMsg = $ErrRecord | Select-Object -Property $SelectedProperties
 615            }
 616
 617            ## Error Invocation Information
 618            If ($GetErrorInvocation) {
 619                If ($ErrRecord.InvocationInfo) {
 620                    [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.InvocationInfo -Property $Property
 621                    $LogErrorInvocationMsg = $ErrRecord.InvocationInfo | Select-Object -Property $SelectedProperties
 622                }
 623            }
 624
 625            ## Capture Error Exception
 626            If ($GetErrorException) {
 627                If ($ErrRecord.Exception) {
 628                    [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrRecord.Exception -Property $Property
 629                    $LogErrorExceptionMsg = $ErrRecord.Exception | Select-Object -Property $SelectedProperties
 630                }
 631            }
 632
 633            ## Display properties in the correct order
 634            If ($Property -eq '*') {
 635                #  If all properties were chosen for display, then arrange them in the order the error object displays them by default.
 636                If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
 637                If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
 638                If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
 639            }
 640            Else {
 641                #  Display selected properties in our custom order
 642                If ($LogErrorExceptionMsg) { [array]$LogErrorMessageTmp += $LogErrorExceptionMsg }
 643                If ($LogErrorRecordMsg) { [array]$LogErrorMessageTmp += $LogErrorRecordMsg }
 644                If ($LogErrorInvocationMsg) { [array]$LogErrorMessageTmp += $LogErrorInvocationMsg }
 645            }
 646
 647            If ($LogErrorMessageTmp) {
 648                $LogErrorMessage = 'Error Record:'
 649                $LogErrorMessage += "`n-------------"
 650                $LogErrorMsg = $LogErrorMessageTmp | Format-List | Out-String
 651                $LogErrorMessage += $LogErrorMsg
 652            }
 653
 654            ## Capture Error Inner Exception(s)
 655            If ($GetErrorInnerException) {
 656                If ($ErrRecord.Exception -and $ErrRecord.Exception.InnerException) {
 657                    $LogInnerMessage = 'Error Inner Exception(s):'
 658                    $LogInnerMessage += "`n-------------------------"
 659
 660                    $ErrorInnerException = $ErrRecord.Exception.InnerException
 661                    $Count = 0
 662
 663                    While ($ErrorInnerException) {
 664                        [string]$InnerExceptionSeperator = '~' * 40
 665
 666                        [string[]]$SelectedProperties = & $SelectProperty -InputObject $ErrorInnerException -Property $Property
 667                        $LogErrorInnerExceptionMsg = $ErrorInnerException | Select-Object -Property $SelectedProperties | Format-List | Out-String
 668
 669                        If ($Count -gt 0) { $LogInnerMessage += $InnerExceptionSeperator }
 670                        $LogInnerMessage += $LogErrorInnerExceptionMsg
 671
 672                        $Count++
 673                        $ErrorInnerException = $ErrorInnerException.InnerException
 674                    }
 675                }
 676            }
 677
 678            If ($LogErrorMessage) { $Output = $LogErrorMessage }
 679            If ($LogInnerMessage) { $Output += $LogInnerMessage }
 680
 681            Write-Output -InputObject $Output
 682
 683            If (Test-Path -LiteralPath 'variable:Output') { Clear-Variable -Name 'Output' }
 684            If (Test-Path -LiteralPath 'variable:LogErrorMessage') { Clear-Variable -Name 'LogErrorMessage' }
 685            If (Test-Path -LiteralPath 'variable:LogInnerMessage') { Clear-Variable -Name 'LogInnerMessage' }
 686            If (Test-Path -LiteralPath 'variable:LogErrorMessageTmp') { Clear-Variable -Name 'LogErrorMessageTmp' }
 687        }
 688    }
 689    End {
 690    }
 691}
 692#endregion
 693
 694#region Function Write-FunctionHeaderOrFooter
 695Function Write-FunctionHeaderOrFooter {
 696<#
 697.SYNOPSIS
 698    Write the function header or footer to the log upon first entering or exiting a function.
 699.DESCRIPTION
 700    Write the "Function Start" message, the bound parameters the function was invoked with, or the "Function End" message when entering or exiting a function.
 701    Messages are debug messages so will only be logged if LogDebugMessage option is enabled in XML config file.
 702.PARAMETER CmdletName
 703    The name of the function this function is invoked from.
 704.PARAMETER CmdletBoundParameters
 705    The bound parameters of the function this function is invoked from.
 706.PARAMETER Header
 707    Write the function header.
 708.PARAMETER Footer
 709    Write the function footer.
 710.EXAMPLE
 711    Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
 712.EXAMPLE
 713    Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
 714.NOTES
 715    This is an internal script function and should typically not be called directly.
 716.LINK
 717    https://psappdeploytoolkit.com
 718#>
 719    [CmdletBinding()]
 720    Param (
 721        [Parameter(Mandatory = $true)]
 722        [ValidateNotNullorEmpty()]
 723        [string]$CmdletName,
 724        [Parameter(Mandatory = $true, ParameterSetName = 'Header')]
 725        [AllowEmptyCollection()]
 726        [hashtable]$CmdletBoundParameters,
 727        [Parameter(Mandatory = $true, ParameterSetName = 'Header')]
 728        [switch]$Header,
 729        [Parameter(Mandatory = $true, ParameterSetName = 'Footer')]
 730        [switch]$Footer
 731    )
 732
 733    If ($Header) {
 734        Write-Log -Message 'Function Start' -Source ${CmdletName} -DebugMessage
 735
 736        ## Get the parameters that the calling function was invoked with
 737        [string]$CmdletBoundParameters = $CmdletBoundParameters | Format-Table -Property @{ Label = 'Parameter'; Expression = { "[-$($_.Key)]" } }, @{ Label = 'Value'; Expression = { $_.Value }; Alignment = 'Left' } -AutoSize -Wrap | Out-String
 738        If ($CmdletBoundParameters) {
 739            Write-Log -Message "Function invoked with bound parameter(s): `n$CmdletBoundParameters" -Source ${CmdletName} -DebugMessage
 740        }
 741        Else {
 742            Write-Log -Message 'Function invoked without any bound parameters.' -Source ${CmdletName} -DebugMessage
 743        }
 744    }
 745    ElseIf ($Footer) {
 746        Write-Log -Message 'Function End' -Source ${CmdletName} -DebugMessage
 747    }
 748}
 749#endregion
 750
 751#region Function Write-Log
 752Function Write-Log {
 753<#
 754.SYNOPSIS
 755    Write messages to a log file in CMTrace.exe compatible format or Legacy text file format.
 756.DESCRIPTION
 757    Write messages to a log file in CMTrace.exe compatible format or Legacy text file format and optionally display in the console.
 758.PARAMETER Message
 759    The message to write to the log file or output to the console.
 760.PARAMETER Severity
 761    Defines message type. When writing to console or CMTrace.exe log format, it allows highlighting of message type.
 762    Options: 1 = Information (default), 2 = Warning (highlighted in yellow), 3 = Error (highlighted in red)
 763.PARAMETER Source
 764    The source of the message being logged.
 765.PARAMETER ScriptSection
 766    The heading for the portion of the script that is being executed. Default is: $script:installPhase.
 767.PARAMETER LogType
 768    Choose whether to write a CMTrace.exe compatible log file or a Legacy text log file.
 769.PARAMETER LogFileDirectory
 770    Set the directory where the log file will be saved.
 771    Default is %WINDIR%\Logs\WmiToolkit.
 772.PARAMETER LogFileName
 773    Set the name of the log file.
 774.PARAMETER MaxLogFileSizeMB
 775    Maximum file size limit for log file in megabytes (MB). Default is 10 MB.
 776.PARAMETER WriteHost
 777    Write the log message to the console.
 778.PARAMETER ContinueOnError
 779    Suppress writing log message to console on failure to write message to log file. Default is: $true.
 780.PARAMETER PassThru
 781    Return the message that was passed to the function
 782.PARAMETER DebugMessage
 783    Specifies that the message is a debug message. Debug messages only get logged if -LogDebugMessage is set to $true.
 784.PARAMETER LogDebugMessage
 785    Debug messages only get logged if this parameter is set to $true in the config XML file.
 786.EXAMPLE
 787    Write-Log -Message "Installing patch MS15-031" -Source 'Add-Patch' -LogType 'CMTrace'
 788.EXAMPLE
 789    Write-Log -Message "Script is running on Windows 8" -Source 'Test-ValidOS' -LogType 'Legacy'
 790.NOTES
 791.LINK
 792    https://psappdeploytoolkit.com
 793#>
 794    [CmdletBinding()]
 795    Param (
 796        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
 797        [AllowEmptyCollection()]
 798        [Alias('Text')]
 799        [string[]]$Message,
 800        [Parameter(Mandatory = $false, Position = 1)]
 801        [ValidateRange(1, 3)]
 802        [int16]$Severity = 1,
 803        [Parameter(Mandatory = $false, Position = 2)]
 804        [ValidateNotNull()]
 805        [string]$Source = '',
 806        [Parameter(Mandatory = $false, Position = 3)]
 807        [ValidateNotNullorEmpty()]
 808        [string]$ScriptSection = 'Module',
 809        [Parameter(Mandatory = $false, Position = 4)]
 810        [ValidateSet('CMTrace', 'Legacy')]
 811        [string]$LogType = 'Legacy',
 812        [Parameter(Mandatory = $false, Position = 5)]
 813        [ValidateNotNullorEmpty()]
 814        [string]$LogFileDirectory = $(Join-Path -Path $Env:windir -ChildPath '\Logs\PSWmiToolKit'),
 815        [Parameter(Mandatory = $false, Position = 6)]
 816        [ValidateNotNullorEmpty()]
 817        [string]$LogFileName = 'PSWmiToolKit.log',
 818        [Parameter(Mandatory = $false, Position = 7)]
 819        [ValidateNotNullorEmpty()]
 820        [decimal]$MaxLogFileSizeMB = '5',
 821        [Parameter(Mandatory = $false, Position = 8)]
 822        [ValidateNotNullorEmpty()]
 823        [boolean]$WriteHost = $true,
 824        [Parameter(Mandatory = $false, Position = 9)]
 825        [ValidateNotNullorEmpty()]
 826        [boolean]$ContinueOnError = $true,
 827        [Parameter(Mandatory = $false, Position = 10)]
 828        [switch]$PassThru = $false,
 829        [Parameter(Mandatory = $false, Position = 11)]
 830        [switch]$DebugMessage = $false,
 831        [Parameter(Mandatory = $false, Position = 12)]
 832        [boolean]$LogDebugMessage = $false
 833    )
 834
 835    Begin {
 836        ## Get the name of this function
 837        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
 838
 839        ## Logging Variables
 840        #  Log file date/time
 841        [string]$LogTime = (Get-Date -Format 'HH:mm:ss.fff').ToString()
 842        [string]$LogDate = (Get-Date -Format 'MM-dd-yyyy').ToString()
 843        If (-not (Test-Path -LiteralPath 'variable:LogTimeZoneBias')) { [int32]$script:LogTimeZoneBias = [timezone]::CurrentTimeZone.GetUtcOffset([datetime]::Now).TotalMinutes }
 844        [string]$LogTimePlusBias = $LogTime + $script:LogTimeZoneBias
 845        #  Initialize variables
 846        [boolean]$ExitLoggingFunction = $false
 847        If (-not (Test-Path -LiteralPath 'variable:DisableLogging')) { $DisableLogging = $false }
 848        #  Check if the script section is defined
 849        [boolean]$ScriptSectionDefined = [boolean](-not [string]::IsNullOrEmpty($ScriptSection))
 850        #  Get the file name of the source script
 851        Try {
 852            If ($script:MyInvocation.Value.ScriptName) {
 853                [string]$ScriptSource = Split-Path -Path $script:MyInvocation.Value.ScriptName -Leaf -ErrorAction 'Stop'
 854            }
 855            Else {
 856                [string]$ScriptSource = Split-Path -Path $script:MyInvocation.MyCommand.Definition -Leaf -ErrorAction 'Stop'
 857            }
 858        }
 859        Catch {
 860            $ScriptSource = ''
 861        }
 862
 863        ## Create script block for generating CMTrace.exe compatible log entry
 864        [scriptblock]$CMTraceLogString = {
 865            Param (
 866                [string]$lMessage,
 867                [string]$lSource,
 868                [int16]$lSeverity
 869            )
 870            "<![LOG[$lMessage]LOG]!>" + "<time=`"$LogTimePlusBias`" " + "date=`"$LogDate`" " + "component=`"$lSource`" " + "context=`"$([Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " + "type=`"$lSeverity`" " + "thread=`"$PID`" " + "file=`"$ScriptSource`">"
 871        }
 872
 873        ## Create script block for writing log entry to the console
 874        [scriptblock]$WriteLogLineToHost = {
 875            Param (
 876                [string]$lTextLogLine,
 877                [int16]$lSeverity
 878            )
 879            If ($WriteHost) {
 880                #  Only output using color options if running in a host which supports colors.
 881                If ($Host.UI.RawUI.ForegroundColor) {
 882                    Switch ($lSeverity) {
 883                        3 { Write-Host -Object $lTextLogLine -ForegroundColor 'Red' -BackgroundColor 'Black' }
 884                        2 { Write-Host -Object $lTextLogLine -ForegroundColor 'Yellow' -BackgroundColor 'Black' }
 885                        1 { Write-Host -Object $lTextLogLine }
 886                    }
 887                }
 888                #  If executing "powershell.exe -File <filename>.ps1 > log.txt", then all the Write-Host calls are converted to Write-Output calls so that they are included in the text log.
 889                Else {
 890                    Write-Output -InputObject $lTextLogLine
 891                }
 892            }
 893        }
 894
 895        ## Exit function if it is a debug message and logging debug messages is not enabled in the config XML file
 896        If (($DebugMessage) -and (-not $LogDebugMessage)) { [boolean]$ExitLoggingFunction = $true; Return }
 897        ## Exit function if logging to file is disabled and logging to console host is disabled
 898        If (($DisableLogging) -and (-not $WriteHost)) { [boolean]$ExitLoggingFunction = $true; Return }
 899        ## Exit Begin block if logging is disabled
 900        If ($DisableLogging) { Return }
 901        ## Exit function function if it is an [Initialization] message and the toolkit has been relaunched
 902        If ($ScriptSection -eq 'Initialization') { [boolean]$ExitLoggingFunction = $true; Return }
 903
 904        ## Create the directory where the log file will be saved
 905        If (-not (Test-Path -LiteralPath $LogFileDirectory -PathType 'Container')) {
 906            Try {
 907                $null = New-Item -Path $LogFileDirectory -Type 'Directory' -Force -ErrorAction 'Stop'
 908            }
 909            Catch {
 910                [boolean]$ExitLoggingFunction = $true
 911                #  If error creating directory, write message to console
 912                If (-not $ContinueOnError) {
 913                    Write-Host -Object "[$LogDate $LogTime] [${CmdletName}] $ScriptSection :: Failed to create the log directory [$LogFileDirectory]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 914                }
 915                Return
 916            }
 917        }
 918
 919        ## Assemble the fully qualified path to the log file
 920        [string]$LogFilePath = Join-Path -Path $LogFileDirectory -ChildPath $LogFileName
 921    }
 922    Process {
 923        ## Exit function if logging is disabled
 924        If ($ExitLoggingFunction) { Return }
 925
 926        ForEach ($Msg in $Message) {
 927            ## If the message is not $null or empty, create the log entry for the different logging methods
 928            [string]$CMTraceMsg = ''
 929            [string]$ConsoleLogLine = ''
 930            [string]$LegacyTextLogLine = ''
 931            If ($Msg) {
 932                #  Create the CMTrace log message
 933                If ($ScriptSectionDefined) { [string]$CMTraceMsg = "[$ScriptSection] :: $Msg" }
 934
 935                #  Create a Console and Legacy "text" log entry
 936                [string]$LegacyMsg = "[$LogDate $LogTime]"
 937                If ($ScriptSectionDefined) { [string]$LegacyMsg += " [$ScriptSection]" }
 938                If ($Source) {
 939                    [string]$ConsoleLogLine = "$LegacyMsg [$Source] :: $Msg"
 940                    Switch ($Severity) {
 941                        3 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Error] :: $Msg" }
 942                        2 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Warning] :: $Msg" }
 943                        1 { [string]$LegacyTextLogLine = "$LegacyMsg [$Source] [Info] :: $Msg" }
 944                    }
 945                }
 946                Else {
 947                    [string]$ConsoleLogLine = "$LegacyMsg :: $Msg"
 948                    Switch ($Severity) {
 949                        3 { [string]$LegacyTextLogLine = "$LegacyMsg [Error] :: $Msg" }
 950                        2 { [string]$LegacyTextLogLine = "$LegacyMsg [Warning] :: $Msg" }
 951                        1 { [string]$LegacyTextLogLine = "$LegacyMsg [Info] :: $Msg" }
 952                    }
 953                }
 954            }
 955
 956            ## Execute script block to create the CMTrace.exe compatible log entry
 957            [string]$CMTraceLogLine = & $CMTraceLogString -lMessage $CMTraceMsg -lSource $Source -lSeverity $Severity
 958
 959            ## Choose which log type to write to file
 960            If ($LogType -ieq 'CMTrace') {
 961                [string]$LogLine = $CMTraceLogLine
 962            }
 963            Else {
 964                [string]$LogLine = $LegacyTextLogLine
 965            }
 966
 967            ## Write the log entry to the log file if logging is not currently disabled
 968            If (-not $DisableLogging) {
 969                Try {
 970                    $LogLine | Out-File -FilePath $LogFilePath -Append -NoClobber -Force -Encoding 'UTF8' -ErrorAction 'Stop'
 971                }
 972                Catch {
 973                    If (-not $ContinueOnError) {
 974                        Write-Host -Object "[$LogDate $LogTime] [$ScriptSection] [${CmdletName}] :: Failed to write message [$Msg] to the log file [$LogFilePath]. `n$(Resolve-Error)" -ForegroundColor 'Red'
 975                    }
 976                }
 977            }
 978
 979            ## Execute script block to write the log entry to the console if $WriteHost is $true
 980            & $WriteLogLineToHost -lTextLogLine $ConsoleLogLine -lSeverity $Severity
 981        }
 982    }
 983    End {
 984        ## Archive log file if size is greater than $MaxLogFileSizeMB and $MaxLogFileSizeMB > 0
 985        Try {
 986            If ((-not $ExitLoggingFunction) -and (-not $DisableLogging)) {
 987                [IO.FileInfo]$LogFile = Get-ChildItem -LiteralPath $LogFilePath -ErrorAction 'Stop'
 988                [decimal]$LogFileSizeMB = $LogFile.Length / 1MB
 989                If (($LogFileSizeMB -gt $MaxLogFileSizeMB) -and ($MaxLogFileSizeMB -gt 0)) {
 990                    ## Change the file extension to "lo_"
 991                    [string]$ArchivedOutLogFile = [IO.Path]::ChangeExtension($LogFilePath, 'lo_')
 992                    [hashtable]$ArchiveLogParams = @{ ScriptSection = $ScriptSection; Source = ${CmdletName}; Severity = 2; LogFileDirectory = $LogFileDirectory; LogFileName = $LogFileName; LogType = $LogType; MaxLogFileSizeMB = 0; WriteHost = $WriteHost; ContinueOnError = $ContinueOnError; PassThru = $false }
 993
 994                    ## Log message about archiving the log file
 995                    $ArchiveLogMessage = "Maximum log file size [$MaxLogFileSizeMB MB] reached. Rename log file to [$ArchivedOutLogFile]."
 996                    Write-Log -Message $ArchiveLogMessage @ArchiveLogParams
 997
 998                    ## Archive existing log file from <filename>.log to <filename>.lo_. Overwrites any existing <filename>.lo_ file. This is the same method SCCM uses for log files.
 999                    Move-Item -LiteralPath $LogFilePath -Destination $ArchivedOutLogFile -Force -ErrorAction 'Stop'
1000
1001                    ## Start new log file and Log message about archiving the old log file
1002                    $NewLogMessage = "Previous log file was renamed to [$ArchivedOutLogFile] because maximum log file size of [$MaxLogFileSizeMB MB] was reached."
1003                    Write-Log -Message $NewLogMessage @ArchiveLogParams
1004                }
1005            }
1006        }
1007        Catch {
1008            ## If renaming of file fails, script will continue writing to log file even if size goes over the max file size
1009        }
1010        Finally {
1011            If ($PassThru) { Write-Output -InputObject $Message }
1012        }
1013    }
1014}
1015#endregion
1016
1017#region Function Get-WmiClass
1018Function Get-WmiClass {
1019<#
1020.SYNOPSIS
1021    This function is used to get WMI class details.
1022.DESCRIPTION
1023    This function is used to get the details of one or more WMI classes.
1024.PARAMETER Namespace
1025    Specifies the namespace where to search for the WMI class. Default is: 'ROOT\cimv2'.
1026.PARAMETER ClassName
1027    Specifies the class name to search for. Supports wildcards. Default is: '*'.
1028.PARAMETER QualifierName
1029    Specifies the qualifier name to search for.(Optional)
1030.PARAMETER IncludeSpecialClasses
1031    Specifies to include System, MSFT and CIM classes. Use this or Get operations only.
1032.EXAMPLE
1033    Get-WmiClass -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone'
1034.EXAMPLE
1035    Get-WmiClass -Namespace 'ROOT\SCCM' -QualifierName 'Description'
1036.EXAMPLE
1037    Get-WmiClass -Namespace 'ROOT\SCCM'
1038.INPUTS
1039    None.
1040.OUTPUTS
1041    None.
1042.NOTES
1043    This is a module function and can typically be called directly.
1044.LINK
1045    https://MEMZ.one/PsWmiToolkit
1046.LINK
1047    https://MEMZ.one/PsWmiToolkit-GIT
1048.LINK
1049    https://MEMZ.one/PsWmiToolkit-ISSUES
1050.COMPONENT
1051    WMI
1052.FUNCTIONALITY
1053    WMI Management
1054#>
1055    [CmdletBinding()]
1056    Param (
1057        [Parameter(Mandatory = $false, Position = 0)]
1058        [ValidateNotNullorEmpty()]
1059        [string]$Namespace = 'ROOT\cimv2',
1060        [Parameter(Mandatory = $false, Position = 1)]
1061        [ValidateNotNullorEmpty()]
1062        [string]$ClassName = '*',
1063        [Parameter(Mandatory = $false, Position = 2)]
1064        [ValidateNotNullorEmpty()]
1065        [string]$QualifierName,
1066        [Parameter(Mandatory = $false, Position = 3)]
1067        [ValidateNotNullorEmpty()]
1068        [switch]$IncludeSpecialClasses
1069    )
1070
1071    Begin {
1072        ## Get the name of this function and write header
1073        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1074        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1075    }
1076    Process {
1077        Try {
1078
1079            ## Check if the namespace exists
1080            $NamespaceTest = Get-WmiNamespace -Namespace $Namespace -ErrorAction 'SilentlyContinue'
1081            If (-not $NamespaceTest) {
1082                $NamespaceNotFoundErr = "Namespace [$Namespace] not found."
1083                Write-Log -Message $NamespaceNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1084                Write-Error -Message $NamespaceNotFoundErr -Category 'ObjectNotFound'
1085            }
1086
1087            ## Get all class details
1088            If ($QualifierName) {
1089                $WmiClass = Get-CimClass -Namespace $Namespace -Class $ClassName -QualifierName $QualifierName -ErrorAction 'SilentlyContinue'
1090            }
1091            Else {
1092                $WmiClass = Get-CimClass -Namespace $Namespace -Class $ClassName -ErrorAction 'SilentlyContinue'
1093            }
1094
1095            ## Filter class or classes details based on specified parameters
1096            If ($IncludeSpecialClasses) {
1097                $GetClass = $WmiClass
1098            }
1099            Else {
1100                $GetClass = $WmiClass | Where-Object { ($_.CimClassName -notmatch '__') -and ($_.CimClassName -notmatch 'CIM_') -and ($_.CimClassName -notmatch 'MSFT_') }
1101            }
1102
1103            ## If no class is found, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1104            If (-not $GetClass) {
1105                $ClassNotFoundErr = "No class [$ClassName] found in namespace [$Namespace]."
1106                Write-Log -Message $ClassNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1107                Write-Error -Message $ClassNotFoundErr -Category 'ObjectNotFound'
1108            }
1109        }
1110        Catch {
1111            Write-Log -Message "Failed to retrieve wmi class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1112            Break
1113        }
1114        Finally {
1115
1116            ## If we have anyting to return, add typename for formatting purposes, otherwise set the result to $null
1117            If ($GetClass) {
1118                $GetClass.PSObject.TypeNames.Insert(0, 'Get.WmiClass.Typename')
1119            }
1120            Else {
1121                $GetClass = $null
1122            }
1123
1124            ## Return result
1125            Write-Output -InputObject $GetClass
1126        }
1127    }
1128    End {
1129        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1130    }
1131}
1132#endregion
1133
1134#region Function Get-WmiProperty
1135Function Get-WmiProperty {
1136<#
1137.SYNOPSIS
1138    This function is used to get the properties of a WMI class.
1139.DESCRIPTION
1140    This function is used to get one or more properties of a WMI class.
1141.PARAMETER Namespace
1142    Specifies the namespace where to search for the WMI class. Default is: 'ROOT\cimv2'.
1143.PARAMETER ClassName
1144    Specifies the class name for which to get the properties.
1145.PARAMETER PropertyName
1146    Specifies the propery name to search for. Supports wildcards. Default is: '*'.
1147.PARAMETER PropertyValue
1148    Specifies the propery value or values to search for. Supports wildcards.(Optional)
1149.PARAMETER QualifierName
1150    Specifies the property qualifier name to match. Supports wildcards.(Optional)
1151.PARAMETER Property
1152    Matches property Name, Value and CimType. Can be piped. If this parameter is specified all other search parameters will be ignored.(Optional)
1153    Supported format:
1154        [PSCustomobject]@{
1155            'Name' = 'Website'
1156            'Value' = $null
1157            'CimType' = 'String'
1158        }
1159.EXAMPLE
1160    Get-WmiProperty -Namespace 'ROOT' -ClassName 'SCCMZone'
1161.EXAMPLE
1162    Get-WmiProperty -Namespace 'ROOT' -ClassName 'SCCMZone' -PropertyName 'WebsiteSite' -QualifierName 'key'
1163.EXAMPLE
1164    Get-WmiProperty -Namespace 'ROOT' -ClassName 'SCCMZone' -PropertyName '*Site'
1165.EXAMPLE
1166    $Property = [PSCustomobject]@{
1167        'Name' = 'Website'
1168        'Value' = $null
1169        'CimType' = 'String'
1170    }
1171    Get-WmiProperty -Namespace 'ROOT' -ClassName 'SCCMZone' -Property $Property
1172    $Property | Get-WmiProperty -Namespace 'ROOT' -ClassName 'SCCMZone'
1173.NOTES
1174    This is a module function and can typically be called directly.
1175.LINK
1176    https://MEMZ.one/PsWmiToolkit
1177.LINK
1178    https://MEMZ.one/PsWmiToolkit-GIT
1179.LINK
1180    https://MEMZ.one/PsWmiToolkit-ISSUES
1181#>
1182    [CmdletBinding()]
1183    Param (
1184        [Parameter(Mandatory = $false, Position = 0)]
1185        [ValidateNotNullorEmpty()]
1186        [string]$Namespace = 'ROOT\cimv2',
1187        [Parameter(Mandatory = $true, Position = 1)]
1188        [ValidateNotNullorEmpty()]
1189        [string]$ClassName,
1190        [Parameter(Mandatory = $false, Position = 2)]
1191        [ValidateNotNullorEmpty()]
1192        [string]$PropertyName = '*',
1193        [Parameter(Mandatory = $false, Position = 3)]
1194        [ValidateNotNullorEmpty()]
1195        [string]$PropertyValue,
1196        [Parameter(Mandatory = $false, Position = 4)]
1197        [ValidateNotNullorEmpty()]
1198        [string]$QualifierName,
1199        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 5)]
1200        [ValidateNotNullorEmpty()]
1201        [PSCustomObject]$Property = @()
1202    )
1203
1204    Begin {
1205        ## Get the name of this function and write header
1206        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1207        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1208    }
1209    Process {
1210        Try {
1211
1212            ## Check if class exists
1213            $ClassTest = Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'SilentlyContinue'
1214
1215            ## If no class is found, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1216            If (-not $ClassTest) {
1217                $ClassNotFoundErr = "No class [$ClassName] found in namespace [$Namespace]."
1218                Write-Log -Message $ClassNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1219                Write-Error -Message $ClassNotFoundErr -Category 'ObjectNotFound'
1220            }
1221
1222            ## Get class properties
1223            $WmiProperty = (Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'SilentlyContinue' | Select-Object *).CimClassProperties | Where-Object -Property Name -Like $PropertyName
1224
1225            ## Get class property based on specified parameters
1226            If ($Property) {
1227
1228                #  Compare all specified properties and return only properties that match Name, Value and CimType.
1229                $GetProperty = Compare-Object -ReferenceObject $Property -DifferenceObject $WmiProperty -Property Name, Value, CimType -IncludeEqual -ExcludeDifferent -PassThru
1230
1231            }
1232            ElseIf ($PropertyValue -and $QualifierName) {
1233                $GetProperty = $WmiProperty | Where-Object { ($_.Value -like $PropertyValue) -and ($_.Qualifiers.Name -like $QualifierName) }
1234            }
1235            ElseIf ($PropertyValue) {
1236                $GetProperty = $WmiProperty | Where-Object -Property Value -Like $PropertyValue
1237            }
1238            ElseIf ($QualifierName) {
1239                $GetProperty = $WmiProperty | Where-Object { $_.Qualifiers.Name -like $QualifierName }
1240            }
1241            Else {
1242                $GetProperty = $WmiProperty
1243            }
1244
1245            ## If no matching properties are found, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1246            If (-not $GetProperty) {
1247                $PropertyNotFoundErr = "No property [$PropertyName] found for class [$Namespace`:$ClassName]."
1248                Write-Log -Message $PropertyNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1249                Write-Error -Message $PropertyNotFoundErr -Category 'ObjectNotFound'
1250            }
1251        }
1252        Catch {
1253            Write-Log -Message "Failed to retrieve wmi class [$Namespace`:$ClassName] properties. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1254            Break
1255        }
1256        Finally {
1257            Write-Output -InputObject $GetProperty
1258        }
1259    }
1260    End {
1261        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1262    }
1263}
1264#endregion
1265
1266#region Function Get-WmiNamespace
1267Function Get-WmiNamespace {
1268<#
1269.SYNOPSIS
1270    This function is used to get WMI namespace information.
1271.DESCRIPTION
1272    This function is used to get the details of one or more WMI namespaces.
1273.PARAMETER Namespace
1274    Specifies the namespace(s) path(s). Supports wildcards only when not using the -Recurse or -List switch. Can be piped.
1275.PARAMETER List
1276    This switch is used to list all namespaces in the specified path. Cannot be used in conjunction with the -Recurse switch.
1277.PARAMETER Recurse
1278    This switch is used to get the whole WMI namespace tree recursively. Cannot be used in conjunction with the -List switch.
1279.EXAMPLE
1280    C:\PS> Get-WmiNamespace -NameSpace 'ROOT\SCCM'
1281.EXAMPLE
1282    C:\PS> Get-WmiNamespace -NameSpace 'ROOT\*CM'
1283.EXAMPLE
1284    C:\PS> Get-WmiNamespace -NameSpace 'ROOT' -List
1285.EXAMPLE
1286    C:\PS> Get-WmiNamespace -NameSpace 'ROOT' -Recurse
1287.EXAMPLE
1288    C:\PS> 'Root\SCCM', 'Root\SC*' | Get-WmiNamespace
1289.INPUTS
1290    System.String[].
1291.OUTPUTS
1292    System.Management.Automation.PSCustomObject.
1293        'Name'
1294        'Path'
1295        'FullName'
1296.NOTES
1297    This is a public module function and can typically be called directly.
1298.LINK
1299    https://MEMZ.one/PsWmiToolkit
1300.LINK
1301    https://MEMZ.one/PsWmiToolkit-GIT
1302.LINK
1303    https://MEMZ.one/PsWmiToolkit-ISSUES
1304.COMPONENT
1305    WMI
1306.FUNCTIONALITY
1307    WMI Management
1308#>
1309    [CmdletBinding()]
1310    Param (
1311        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
1312        [ValidateNotNullorEmpty()]
1313        [SupportsWildcards()]
1314        [string[]]$Namespace,
1315        [Parameter(Mandatory = $false, Position = 1)]
1316        [ValidateNotNullorEmpty()]
1317        [ValidateScript({
1318                If ($Namespace -match '\*') { Throw 'Wildcards are not supported with this switch.' }
1319                Return $true
1320            })]
1321        [switch]$List = $false,
1322        [Parameter(Mandatory = $false, Position = 2)]
1323        [ValidateNotNullorEmpty()]
1324        [ValidateScript({
1325                If ($Namespace -match '\*') { Throw 'Wildcards are not supported with this switch.' }
1326                Return $true
1327            })]
1328        [switch]$Recurse = $false
1329    )
1330
1331    Begin {
1332        ## Get the name of this function and write header
1333        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1334        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1335
1336        ## Initialize result variable
1337        [PSCustomObject]$GetNamespace = $null
1338    }
1339    Process {
1340        Try {
1341
1342            ## Get namespace tree recursively if specified, otherwise just get the current namespace
1343            If ($Recurse) {
1344
1345                #  Call Get-WmiNamespaceRecursive internal function
1346                $GetNamespace = Get-WmiNamespaceRecursive -Namespace $Namespace -ErrorAction 'SilentlyContinue' | Sort-Object -Property Path
1347            }
1348            Else {
1349
1350                ## If namespace is 'ROOT' or -List is specified get namespace else get Parent\Leaf namespace
1351                If ($List -or ($Namespace -eq 'ROOT')) {
1352                    $WmiNamespace = Get-CimInstance -Namespace $([string]$Namespace) -ClassName '__Namespace' -ErrorAction 'SilentlyContinue' -ErrorVariable Err
1353                }
1354                Else {
1355                    #  Set namespace path and name
1356                    [string]$NamespaceParent = $(Split-Path -Path $Namespace -Parent)
1357                    [string]$NamespaceLeaf = $(Split-Path -Path $Namespace -Leaf)
1358                    #  Get namespace
1359                    $WmiNamespace = Get-CimInstance -Namespace $NamespaceParent -ClassName '__Namespace' -ErrorAction 'SilentlyContinue' -ErrorVariable Err | Where-Object { $_.Name -like $NamespaceLeaf }
1360                }
1361
1362                ## If no namespace is found, write debug message and optionally throw error is -ErrorAction 'Stop' is specified
1363                If (-not $WmiNamespace -and $List -and (-not $Err)) {
1364                    $NamespaceChildrenNotFoundErr = "Namespace [$Namespace] has no children."
1365                    Write-Log -Message $NamespaceChildrenNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1366                    Write-Error -Message $NamespaceChildrenNotFoundErr -Category 'ObjectNotFound'
1367                }
1368                ElseIf (-not $WmiNamespace) {
1369                    $NamespaceNotFoundErr = "Namespace [$Namespace] not found."
1370                    Write-Log -Message $NamespaceNotFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1371                    Write-Error -Message $NamespaceNotFoundErr -Category 'ObjectNotFound'
1372                }
1373                ElseIf (-not $Err) {
1374                    $GetNamespace = $WmiNamespace | ForEach-Object {
1375                        [PSCustomObject]@{
1376                            Name     = $Name = $_.Name
1377                            #  Standardize namespace path separator by changing it from '/' to '\'.
1378                            Path     = $Path = $_.CimSystemProperties.Namespace -replace ('/', '\')
1379                            FullName = "$Path`\$Name"
1380                        }
1381                    }
1382                }
1383            }
1384        }
1385        Catch {
1386            Write-Log -Message "Failed to retrieve wmi namespace [$Namespace]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1387            Break
1388        }
1389        Finally {
1390
1391            ## If we have anyting to return, add typename for formatting purposes, otherwise set the result to $null
1392            If ($GetNamespace) {
1393                $GetNamespace.PSObject.TypeNames.Insert(0, 'Get.WmiNamespace.Typename')
1394            }
1395            Else {
1396                $GetNamespace = $null
1397            }
1398
1399            ## Return result
1400            Write-Output -InputObject $GetNamespace
1401        }
1402    }
1403    End {
1404        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1405    }
1406}
1407#endregion
1408
1409#region Function Get-WmiNamespaceRecursive
1410Function Get-WmiNamespaceRecursive {
1411<#
1412.SYNOPSIS
1413    This function is used to get wmi namespaces recursively.
1414.DESCRIPTION
1415    This function is used to get wmi namespaces recursively and returns a custom object.
1416.PARAMETER Namespace
1417    Specifies the root namespace(s) path(s) to search. Cand be piped.
1418.EXAMPLE
1419    C:\PS> $Result = Get-WmiNamespaceRecursive -NameSpace 'ROOT\SCCM'
1420.EXAMPLE
1421    C:\PS> $Result = 'ROOT\SCCM', 'ROOT\Appv' | Get-WmiNamespaceRecursive
1422.INPUTS
1423    System.String[].
1424.OUTPUTS
1425    System.Management.Automation.PSCustomObject.
1426        'Name'
1427        'Path'
1428        'FullName'
1429.NOTES
1430    As this is a recursive function it will run multiple times so you might want to assign it to a variable for sorting.
1431    You also might want to disable logging when running this function.
1432
1433    This is an internal module function and should not typically be called directly.
1434.LINK
1435    https://MEMZ.one/PsWmiToolkit
1436.LINK
1437    https://MEMZ.one/PsWmiToolkit-GIT
1438.LINK
1439    https://MEMZ.one/PsWmiToolkit-ISSUES
1440.COMPONENT
1441    WMI
1442.FUNCTIONALITY
1443    WMI Management
1444#>
1445    [CmdletBinding()]
1446    Param (
1447        [Parameter(Mandatory = $true, ValueFromPipeline, Position = 0)]
1448        [ValidateNotNullorEmpty()]
1449        [string[]]$Namespace
1450    )
1451
1452    Begin {
1453        ## Initialize/Reset result object
1454        [PSCustomObject]$GetNamespaceRecursive = @()
1455    }
1456    Process {
1457        Try {
1458
1459            ## Get all namespaces in the current root namespace
1460            $Namespaces = Get-WmiNamespace -Namespace $Namespace -List
1461
1462            ## Search in the current namespace for other namespaces
1463            If ($Namespaces) {
1464                $Namespaces | ForEach-Object {
1465                    #  Assemble the result object
1466                    $GetNamespaceRecursive += [PsCustomObject]@{
1467                        Name     = $_.Name
1468                        Path     = $_.Path
1469                        FullName = $_.FullName
1470                    }
1471
1472                    #  Call the function again for the next namespace
1473                    Get-WmiNamespaceRecursive -Namespace $_.FullName
1474                }
1475            }
1476        }
1477        Catch {
1478            Write-Log -Message "Failed to retrieve wmi namespace [$Namespace] recursively. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1479            Break
1480        }
1481    }
1482    End {
1483        Write-Output -InputObject $GetNamespaceRecursive
1484    }
1485}
1486#endregion
1487
1488#region Function New-WmiClass
1489Function New-WmiClass {
1490<#
1491.SYNOPSIS
1492    This function is used to create a WMI class.
1493.DESCRIPTION
1494    This function is used to create a WMI class with custom properties.
1495.PARAMETER Namespace
1496    Specifies the namespace where to search for the WMI namespace. Default is: 'ROOT\cimv2'.
1497.PARAMETER ClassName
1498    Specifies the name for the new class.
1499.PARAMETER Qualifiers
1500    Specifies one ore more property qualifiers using qualifier name and value only. You can omit this parameter or enter one or more items in the hashtable.
1501    You can also specify a string but you must separate the name and value with a new line character (`n). This parameter can also be piped.
1502    The qualifiers will be added with these default values and flavors:
1503        Static = $true
1504        IsAmended = $false
1505        PropagatesToInstance = $true
1506        PropagatesToSubClass = $false
1507        IsOverridable = $true
1508.PARAMETER CreateDestination
1509    This switch is used to create destination namespace.
1510.EXAMPLE
1511    [hashtable]$Qualifiers = @{
1512        Key = $true
1513        Static = $true
1514        Description = 'SCCMZone Blog'
1515    }
1516    New-WmiClass -Namespace 'ROOT' -ClassName 'SCCMZone' -Qualifiers $Qualifiers
1517.EXAMPLE
1518    "Key = $true `n Static = $true `n Description = SCCMZone Blog" | New-WmiClass -Namespace 'ROOT' -ClassName 'SCCMZone'
1519.EXAMPLE
1520    New-WmiClass -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -CreateDestination
1521.NOTES
1522    This is a module function and can typically be called directly.
1523.LINK
1524    https://MEMZ.one/PsWmiToolkit
1525.LINK
1526    https://MEMZ.one/PsWmiToolkit-GIT
1527.LINK
1528    https://MEMZ.one/PsWmiToolkit-ISSUES
1529#>
1530    [CmdletBinding()]
1531    Param (
1532        [Parameter(Mandatory = $false, Position = 0)]
1533        [ValidateNotNullorEmpty()]
1534        [string]$Namespace = 'ROOT\cimv2',
1535        [Parameter(Mandatory = $true, Position = 1)]
1536        [ValidateNotNullorEmpty()]
1537        [string]$ClassName,
1538        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 2)]
1539        [ValidateNotNullorEmpty()]
1540        [PSCustomObject]$Qualifiers = @("Static = $true"),
1541        [Parameter(Mandatory = $false, Position = 3)]
1542        [ValidateNotNullorEmpty()]
1543        [switch]$CreateDestination = $false
1544    )
1545
1546    Begin {
1547        ## Get the name of this function and write header
1548        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1549        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1550    }
1551    Process {
1552        Try {
1553
1554            ## Check if the class exists
1555            [boolean]$ClassTest = Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'SilentlyContinue'
1556
1557            ## Check if the namespace exists
1558            [boolean]$NamespaceTest = Get-WmiNamespace -Namespace $Namespace -ErrorAction 'SilentlyContinue'
1559
1560            ## Create destination namespace if specified, otherwise throw error if -ErrorAction 'Stop' is specified
1561            If ((-not $NamespaceTest) -and $CreateDestination) {
1562                $null = New-WmiNamespace $Namespace -CreateSubTree -ErrorAction 'Stop'
1563            }
1564            ElseIf (-not $NamespaceTest) {
1565                $NamespaceNotFoundErr = "Namespace [$Namespace] does not exist. Use the -CreateDestination switch to create namespace."
1566                Write-Log -Message $NamespaceNotFoundErr -Severity 3 -Source ${CmdletName}
1567                Write-Error -Message $NamespaceNotFoundErr -Category 'ObjectNotFound'
1568            }
1569
1570            ## Create class if it does not exist
1571            If (-not $ClassTest) {
1572
1573                #  Create class object
1574                [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:__CLASS", [String]::Empty, $null)
1575                $ClassObject.Name = $ClassName
1576
1577                #  Write the class and dispose of the class object
1578                $NewClass = $ClassObject.Put()
1579                $ClassObject.Dispose()
1580
1581                #  On class creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1582                If (-not $NewClass) {
1583
1584                    #  Error handling and logging
1585                    $NewClassErr = "Failed to create class [$ClassName] in namespace [$Namespace]."
1586                    Write-Log -Message $NewClassErr -Severity 3 -Source ${CmdletName} -DebugMessage
1587                    Write-Error -Message $NewClassErr -Category 'InvalidResult'
1588                }
1589
1590                ## If input qualifier is not a hashtable convert string input to hashtable
1591                If ($Qualifiers -isnot [hashtable]) {
1592                    $Qualifiers = $Qualifiers | ConvertFrom-StringData
1593                }
1594
1595                ## Set property qualifiers one by one if specified, otherwise set default qualifier name, value and flavors
1596                If ($Qualifiers) {
1597                    #  Convert to a hashtable format accepted by Set-WmiClassQualifier. Name = QualifierName and Value = QualifierValue are expected.
1598                    $Qualifiers.Keys | ForEach-Object {
1599                        [hashtable]$PropertyQualifier = @{ Name = $_; Value = $Qualifiers.Item($_) }
1600                        #  Set qualifier
1601                        $null = Set-WmiClassQualifier -Namespace $Namespace -ClassName $ClassName -Qualifier $PropertyQualifier -ErrorAction 'Stop'
1602                    }
1603                }
1604                Else {
1605                    $null = Set-WmiClassQualifier -Namespace $Namespace -ClassName $ClassName -ErrorAction 'Stop'
1606                }
1607            }
1608            Else {
1609                $ClassAlreadyExistsErr = "Failed to create class [$Namespace`:$ClassName]. Class already exists."
1610                Write-Log -Message $ClassAlreadyExistsErr -Severity 2 -Source ${CmdletName} -DebugMessage
1611                Write-Error -Message $ClassAlreadyExistsErr -Category 'ResourceExists'
1612            }
1613        }
1614        Catch {
1615            Write-Log -Message "Failed to create class [$ClassName] in namespace [$Namespace]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1616            Break
1617        }
1618        Finally {
1619            Write-Output -InputObject $NewClass
1620        }
1621    }
1622    End {
1623        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1624    }
1625}
1626#endregion
1627
1628#region Function New-WmiInstance
1629Function New-WmiInstance {
1630<#
1631.SYNOPSIS
1632    This function is used to create a WMI Instance.
1633.DESCRIPTION
1634    This function is used to create a WMI Instance using CIM.
1635.PARAMETER Namespace
1636    Specifies the namespace where to search for the WMI class. Default is: 'ROOT\cimv2'.
1637.PARAMETER ClassName
1638    Specifies the class where to create the new WMI instance.
1639.PARAMETER Key
1640    Specifies properties that are used as keys (Optional).
1641.PARAMETER Property
1642    Specifies the class instance Properties or Values. You can also specify a string but you must separate the name and value with a new line character (`n).
1643    This parameter can also be piped.
1644.EXAMPLE
1645    [hashtable]$Property = @{
1646        'ServerPort' = '89'
1647        'ServerIP' = '11.11.11.11'
1648        'Source' = 'File1'
1649        'Date' = $(Get-Date)
1650    }
1651    New-WmiInstance -Namespace 'ROOT' -ClassName 'SCCMZone' -Key 'File1' -Property $Property
1652.EXAMPLE
1653    "Server Port = 89 `n ServerIp = 11.11.11.11 `n Source = File `n Date = $(GetDate)" | New-WmiInstance -Namespace 'ROOT' -ClassName 'SCCMZone' -Property $Property
1654.NOTES
1655    This is a module function and can typically be called directly.
1656.LINK
1657    https://MEMZ.one/PsWmiToolkit
1658.LINK
1659    https://MEMZ.one/PsWmiToolkit-GIT
1660.LINK
1661    https://MEMZ.one/PsWmiToolkit-ISSUES
1662#>
1663    [CmdletBinding()]
1664    Param (
1665        [Parameter(Mandatory = $false, Position = 0)]
1666        [ValidateNotNullorEmpty()]
1667        [string]$Namespace = 'ROOT\cimv2',
1668        [Parameter(Mandatory = $true, Position = 1)]
1669        [ValidateNotNullorEmpty()]
1670        [string]$ClassName,
1671        [Parameter(Mandatory = $false, Position = 2)]
1672        [ValidateNotNullorEmpty()]
1673        [string[]]$Key,
1674        [Parameter(Mandatory = $true, ValueFromPipeline, Position = 3)]
1675        [ValidateNotNullorEmpty()]
1676        [PSCustomObject]$Property
1677    )
1678
1679    Begin {
1680        ## Get the name of this function and write header
1681        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1682        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1683    }
1684    Process {
1685        Try {
1686
1687            ## Check if class exists
1688            $null = Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'Stop'
1689
1690            ## If input qualifier is not a hashtable convert string input to hashtable
1691            If ($Property -isnot [hashtable]) {
1692                $Property = $Property | ConvertFrom-StringData
1693            }
1694
1695            ## Create instance
1696            If ($Key) {
1697                $NewInstance = New-CimInstance -Namespace $Namespace -ClassName $ClassName -Key $Key -Property $Property
1698            }
1699            Else {
1700                $NewInstance = New-CimInstance -Namespace $Namespace -ClassName $ClassName -Property $Property
1701            }
1702
1703            ## On instance creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1704            If (-not $NewInstance) {
1705                Write-Log -Message "Failed to create instance in class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName} -DebugMessage
1706            }
1707        }
1708        Catch {
1709            Write-Log -Message "Failed to create instance in class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1710            Break
1711        }
1712        Finally {
1713            Write-Output -InputObject $NewInstance
1714        }
1715    }
1716    End {
1717        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1718    }
1719}
1720#endregion
1721
1722#region Function New-WmiNamespace
1723Function New-WmiNamespace {
1724<#
1725.SYNOPSIS
1726    This function is used to create a new WMI namespace.
1727.DESCRIPTION
1728    This function is used to create a new WMI namespace.
1729.PARAMETER Namespace
1730    Specifies the namespace to create.
1731.PARAMETER CreateSubTree
1732    This swith is used to create the whole namespace sub tree if it does not exist.
1733.EXAMPLE
1734    New-WmiNamespace -Namespace 'ROOT\SCCM'
1735.EXAMPLE
1736    New-WmiNamespace -Namespace 'ROOT\SCCM\SCCMZone\Blog' -CreateSubTree
1737.NOTES
1738    This is a module function and can typically be called directly.
1739.LINK
1740    https://MEMZ.one/PsWmiToolkit
1741.LINK
1742    https://MEMZ.one/PsWmiToolkit-GIT
1743.LINK
1744    https://MEMZ.one/PsWmiToolkit-ISSUES
1745#>
1746    [CmdletBinding()]
1747    Param (
1748        [Parameter(Mandatory = $true, Position = 0)]
1749        [ValidateNotNullorEmpty()]
1750        [string]$Namespace,
1751        [Parameter(Mandatory = $false, Position = 1)]
1752        [ValidateNotNullorEmpty()]
1753        [switch]$CreateSubTree = $false
1754    )
1755
1756    Begin {
1757        ## Get the name of this function and write header
1758        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1759        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1760    }
1761    Process {
1762        Try {
1763
1764            ## Check if the namespace exists
1765            $WmiNamespace = Get-WmiNamespace -Namespace $Namespace -ErrorAction 'SilentlyContinue'
1766
1767            ## Create Namespace if it does not exist
1768            If (-not $WmiNamespace) {
1769
1770                #  Split path into it's components
1771                $NamespacePaths = $Namespace.Split('\')
1772
1773                #  Assigning root namespace, just for show, should always be 'ROOT'
1774                [string]$Path = $NamespacePaths[0]
1775
1776                #  Initialize NamespacePathsObject
1777                [PSCustomObject]$NamespacePathsObject = @()
1778
1779                #  Parsing path components and assemble individual paths
1780                For ($i = 1; $i -le $($NamespacePaths.Length - 1); $i++ ) {
1781                    $Path += '\' + $NamespacePaths[$i]
1782
1783                    #  Assembing path props and add them to the NamspacePathsObject
1784                    $PathProps = [ordered]@{ Name = $(Split-Path -Path $Path) ; Value = $(Split-Path -Path $Path -Leaf) }
1785                    $NamespacePathsObject += $PathProps
1786                }
1787
1788                #  Split path into it's components
1789                $NamespacePaths = $Namespace.Split('\')
1790
1791                #  Assigning root namespace, just for show, should always be 'ROOT'
1792                [string]$Path = $NamespacePaths[0]
1793
1794                #  Initialize NamespacePathsObject
1795                [PSCustomObject]$NamespacePathsObject = @()
1796
1797                #  Parsing path components and assemble individual paths
1798                For ($i = 1; $i -le $($NamespacePaths.Length - 1); $i++ ) {
1799                    $Path += '\' + $NamespacePaths[$i]
1800
1801                    #  Assembing path props and add them to the NamspacePathsObject
1802                    $PathProps = [ordered]@{
1803                        'NamespacePath' = $(Split-Path -Path $Path)
1804                        'NamespaceName' = $(Split-Path -Path $Path -Leaf)
1805                        'NamespaceTest' = [boolean]$(Get-WmiNamespace -Namespace $Path -ErrorAction 'SilentlyContinue')
1806                    }
1807                    $NamespacePathsObject += [PSCustomObject]$PathProps
1808                }
1809
1810                #  If the path does not contain missing subnamespaces or the -CreateSubTree switch is specified create namespace or namespaces
1811                If ((($NamespacePathsObject -match $false).Count -eq 1 ) -or $CreateSubTree) {
1812
1813                    #  Create each namespace in path one by one
1814                    $NamespacePathsObject | ForEach-Object {
1815
1816                        #  Check if we need to create the namespace
1817                        If (-not $_.NamespaceTest) {
1818                            #  Create namespace object and assign namespace name
1819                            $NameSpaceObject = (New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList "\\.\$($_.NameSpacePath)`:__NAMESPACE").CreateInstance()
1820                            $NameSpaceObject.Name = $_.NamespaceName
1821
1822                            #  Write the namespace object
1823                            $NewNamespace = $NameSpaceObject.Put()
1824                            $NameSpaceObject.Dispose()
1825                        }
1826                        Else {
1827                            Write-Log -Message "Namespace [$($_.NamespacePath)`\$($_.NamespaceName)] already exists." -Severity 2 -Source ${CmdletName} -DebugMessage
1828                        }
1829                    }
1830
1831                    #  On namespace creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1832                    If (-not $NewNamespace) {
1833                        $CreateNamespaceErr = "Failed to create namespace [$($_.NameSpacePath)`\$($_.NamespaceName)]."
1834                        Write-Log -Message $CreateNamespaceErr -Severity 3 -Source ${CmdletName} -DebugMessage
1835                        Write-Error -Message $CreateNamespaceErr -Category 'InvalidResult'
1836                    }
1837                }
1838                ElseIf (($($NamespacePathsObject -match $false).Count -gt 1)) {
1839                    $SubNamespaceFoundErr = "Child namespace detected in namespace path [$Namespace]. Use the -CreateSubtree switch to create the whole path."
1840                    Write-Log -Message $SubNamespaceFoundErr -Severity 2 -Source ${CmdletName} -DebugMessage
1841                    Write-Error -Message $SubNamespaceFoundErr -Category 'InvalidOperation'
1842                }
1843            }
1844            Else {
1845                $NamespaceAlreadyExistsErr = "Failed to create namespace. [$Namespace] already exists."
1846                Write-Log -Message $NamespaceAlreadyExistsErr -Severity 2 -Source ${CmdletName} -DebugMessage
1847                Write-Error -Message $NamespaceAlreadyExistsErr -Category 'ResourceExists'
1848            }
1849        }
1850        Catch {
1851            Write-Log -Message "Failed to create namespace [$Namespace]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1852            Break
1853        }
1854        Finally {
1855            Write-Output -InputObject $NewNamespace
1856        }
1857    }
1858    End {
1859        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1860    }
1861}
1862#endregion
1863
1864#region Function New-WmiProperty
1865Function New-WmiProperty {
1866<#
1867.SYNOPSIS
1868    This function is used to add properties to a WMI class.
1869.DESCRIPTION
1870    This function is used to add custom properties to a WMI class.
1871.PARAMETER Namespace
1872    Specifies the namespace where to search for the WMI namespace. Default is: 'ROOT\cimv2'.
1873.PARAMETER ClassName
1874    Specifies the class name for which to add the properties.
1875.PARAMETER PropertyName
1876    Specifies the property name.
1877.PARAMETER PropertyType
1878    Specifies the property type.
1879.PARAMETER Qualifiers
1880    Specifies one ore more property qualifiers using qualifier name and value only. You can omit this parameter or enter one or more items in the hashtable.
1881    You can also specify a string but you must separate the name and value with a new line character (`n). This parameter can also be piped.
1882    The qualifiers will be added with these default flavors:
1883        IsAmended = $false
1884        PropagatesToInstance = $true
1885        PropagatesToSubClass = $false
1886        IsOverridable = $true
1887.PARAMETER Key
1888    Specifies if the property is key. Default is: false.(Optional)
1889.EXAMPLE
1890    [hashtable]$Qualifiers = @{
1891        Key = $true
1892        Static = $true
1893        Description = 'SCCMZone Blog'
1894    }
1895    New-WmiProperty -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -PropertyName 'Website' -PropertyType 'String' -Qualifiers $Qualifiers
1896.EXAMPLE
1897    "Key = $true `n Description = SCCMZone Blog" | New-WmiProperty -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -PropertyName 'Website' -PropertyType 'String'
1898.NOTES
1899    This is a module function and can typically be called directly.
1900.LINK
1901    https://MEMZ.one/PsWmiToolkit
1902.LINK
1903    https://MEMZ.one/PsWmiToolkit-GIT
1904.LINK
1905    https://MEMZ.one/PsWmiToolkit-ISSUES
1906#>
1907    [CmdletBinding()]
1908    Param (
1909        [Parameter(Mandatory = $false, Position = 0)]
1910        [ValidateNotNullorEmpty()]
1911        [string]$Namespace = 'ROOT\cimv2',
1912        [Parameter(Mandatory = $true, Position = 1)]
1913        [ValidateNotNullorEmpty()]
1914        [string]$ClassName,
1915        [Parameter(Mandatory = $true, Position = 2)]
1916        [ValidateNotNullorEmpty()]
1917        [string]$PropertyName,
1918        [Parameter(Mandatory = $true, Position = 3)]
1919        [ValidateNotNullorEmpty()]
1920        [string]$PropertyType,
1921        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 4)]
1922        [ValidateNotNullorEmpty()]
1923        [PSCustomObject]$Qualifiers = @()
1924    )
1925
1926    Begin {
1927        ## Get the name of this function and write header
1928        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
1929        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
1930    }
1931    Process {
1932        Try {
1933
1934            ## Check if the class exists
1935            $null = Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'Stop'
1936
1937            ## Check if the property exist
1938            $WmiPropertyTest = Get-WmiProperty -Namespace $Namespace -ClassName $ClassName -PropertyName $PropertyName -ErrorAction 'SilentlyContinue'
1939
1940            ## Create the property if it does not exist
1941            If (-not $WmiPropertyTest) {
1942
1943                #  Set property to array if specified
1944                If ($PropertyType -match 'Array') {
1945                    $PropertyType = $PropertyType.Replace('Array', '')
1946                    $PropertyIsArray = $true
1947                }
1948                Else {
1949                    $PropertyIsArray = $false
1950                }
1951
1952                #  Create the ManagementClass object
1953                [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$ClassName")
1954
1955                #  Add class property
1956                $ClassObject.Properties.Add($PropertyName, [System.Management.CimType]$PropertyType, $PropertyIsArray)
1957
1958                #  Write class object
1959                $NewProperty = $ClassObject.Put()
1960                $ClassObject.Dispose()
1961
1962                ## On property creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
1963                If (-not $NewProperty) {
1964
1965                    #  Error handling and logging
1966                    $NewPropertyErr = "Failed create property [$PropertyName] for Class [$Namespace`:$ClassName]."
1967                    Write-Log -Message $NewPropertyErr -Severity 3 -Source ${CmdletName} -DebugMessage
1968                    Write-Error -Message $NewPropertyErr -Category 'InvalidResult'
1969                }
1970
1971                ## Set property qualifiers one by one if specified
1972                If ($Qualifiers) {
1973                    #  Convert to a hashtable format accepted by Set-WmiPropertyQualifier. Name = QualifierName and Value = QualifierValue are expected.
1974                    $Qualifiers.Keys | ForEach-Object {
1975                        [hashtable]$PropertyQualifier = @{ Name = $_; Value = $Qualifiers.Item($_) }
1976                        #  Set qualifier
1977                        $null = Set-WmiPropertyQualifier -Namespace $Namespace -ClassName $ClassName -PropertyName $PropertyName -Qualifier $PropertyQualifier -ErrorAction 'Stop'
1978                    }
1979                }
1980            }
1981            Else {
1982                $PropertyAlreadyExistsErr = "Property [$PropertyName] already present for class [$Namespace`:$ClassName]."
1983                Write-Log -Message $PropertyAlreadyExistsErr  -Severity 2 -Source ${CmdletName} -DebugMessage
1984                Write-Error -Message $PropertyAlreadyExistsErr -Category 'ResourceExists'
1985            }
1986        }
1987        Catch {
1988            Write-Log -Message "Failed to create property for class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
1989            Break
1990        }
1991        Finally {
1992            Write-Output -InputObject $NewProperty
1993        }
1994    }
1995    End {
1996        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
1997    }
1998}
1999#endregion
2000
2001#region Function Remove-WmiClass
2002Function Remove-WmiClass {
2003<#
2004.SYNOPSIS
2005    This function is used to remove a WMI class.
2006.DESCRIPTION
2007    This function is used to remove a WMI class by name.
2008.PARAMETER Namespace
2009    Specifies the namespace where to search for the WMI class. Default is: 'ROOT\cimv2'.
2010.PARAMETER ClassName
2011    Specifies the class name to remove. Can be piped.
2012.PARAMETER RemoveAll
2013    This switch is used to remove all namespace classes.
2014.EXAMPLE
2015    Remove-WmiClass -Namespace 'ROOT' -ClassName 'SCCMZone','SCCMZoneBlog'
2016.EXAMPLE
2017    'SCCMZone','SCCMZoneBlog' | Remove-WmiClass -Namespace 'ROOT'
2018.EXAMPLE
2019    Remove-WmiClass -Namespace 'ROOT' -RemoveAll
2020.NOTES
2021    This is a module function and can typically be called directly.
2022.LINK
2023    https://MEMZ.one/PsWmiToolkit
2024.LINK
2025    https://MEMZ.one/PsWmiToolkit-GIT
2026.LINK
2027    https://MEMZ.one/PsWmiToolkit-ISSUES
2028#>
2029    [CmdletBinding()]
2030    Param (
2031        [Parameter(Mandatory = $false, Position = 0)]
2032        [ValidateNotNullorEmpty()]
2033        [string]$Namespace = 'ROOT\cimv2',
2034        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 1)]
2035        [ValidateNotNullorEmpty()]
2036        [string[]]$ClassName,
2037        [Parameter(Mandatory = $false, Position = 2)]
2038        [ValidateNotNullorEmpty()]
2039        [switch]$RemoveAll = $false
2040    )
2041
2042    Begin {
2043        ## Get the name of this function and write header
2044        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
2045        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
2046    }
2047    Process {
2048        Try {
2049
2050            ## Get classes names
2051            [string[]]$WmiClassNames = (Get-WmiClass -Namespace $Namespace -ErrorAction 'Stop').CimClassName
2052
2053            ## Add classes to deletion string array depending on selected options
2054            If ($RemoveAll) {
2055                $ClassNamesToDelete = $WmiClassNames
2056            }
2057            ElseIf ($ClassName) {
2058                $ClassNamesToDelete = $WmiClassNames | Where-Object { $_ -in $ClassName }
2059            }
2060            Else {
2061                $ClassNameIsNullErr = "ClassName cannot be `$null if -RemoveAll is not specified."
2062                Write-Log -Message $ClassNameIsNullErr -Severity 3 -Source ${CmdletName}
2063                Write-Error -Message $ClassNameIsNullErr -Category 'InvalidArgument'
2064            }
2065
2066            ## Remove classes
2067            If ($ClassNamesToDelete) {
2068                $ClassNamesToDelete | ForEach-Object {
2069
2070                    #  Create the class object
2071                    [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$_")
2072
2073                    #  Remove class
2074                    $null = $ClassObject.Delete()
2075                    $ClassObject.Dispose()
2076                }
2077            }
2078            Else {
2079                $ClassNotFoundErr = "No matching class [$ClassName] found for namespace [$Namespace]."
2080                Write-Log -Message $ClassNotFoundErr -Severity 2 -Source ${CmdletName}
2081                Write-Error -Message $ClassNotFoundErr -Category 'ObjectNotFound'
2082            }
2083        }
2084        Catch {
2085            Write-Log -Message "Failed to remove class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
2086            Break
2087        }
2088        Finally {}
2089    }
2090    End {
2091        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
2092    }
2093}
2094#endregion
2095
2096#region Function Set-WmiClassQualifier
2097Function Set-WmiClassQualifier {
2098<#
2099.SYNOPSIS
2100    This function is used to set qualifiers to a WMI class.
2101.DESCRIPTION
2102    This function is used to set qualifiers to a WMI class. Existing qualifiers with the same name will be overwriten
2103.PARAMETER Namespace
2104    Specifies the namespace where to search for the WMI namespace. Default is: 'ROOT\cimv2'.
2105.PARAMETER ClassName
2106    Specifies the class name for which to add the qualifiers.
2107.PARAMETER Qualifier
2108    Specifies the qualifier name, value and flavours as hashtable. You can omit this parameter or enter one or more items in the hashtable.
2109    You can also specify a string but you must separate the name and value with a new line character (`n). This parameter can also be piped.
2110    If you omit a hashtable item the default item value will be used. Only item values can be specified (right of the '=' sign).
2111    Default is:
2112        [hashtable][ordered]@{
2113            Name = 'Static'
2114            Value = $true
2115            IsAmended = $false
2116            PropagatesToInstance = $true
2117            PropagatesToSubClass = $false
2118            IsOverridable = $true
2119        }
2120.EXAMPLE
2121    Set-WmiClassQualifier -Namespace 'ROOT' -ClassName 'SCCMZone' -Qualifier @{ Name = 'Description'; Value = 'SCCMZone Blog' }
2122.EXAMPLE
2123    Set-WmiClassQualifier -Namespace 'ROOT' -ClassName 'SCCMZone' -Qualifier "Name = Description `n Value = SCCMZone Blog"
2124.EXAMPLE
2125    "Name = Description `n Value = SCCMZone Blog" | Set-WmiClassQualifier -Namespace 'ROOT' -ClassName 'SCCMZone'
2126.NOTES
2127    This is a module function and can typically be called directly.
2128.LINK
2129    https://MEMZ.one/PsWmiToolkit
2130.LINK
2131    https://MEMZ.one/PsWmiToolkit-GIT
2132.LINK
2133    https://MEMZ.one/PsWmiToolkit-ISSUES
2134#>
2135    [CmdletBinding()]
2136    Param (
2137        [Parameter(Mandatory = $false, Position = 0)]
2138        [ValidateNotNullorEmpty()]
2139        [string]$Namespace = 'ROOT\cimv2',
2140        [Parameter(Mandatory = $true, Position = 1)]
2141        [ValidateNotNullorEmpty()]
2142        [string]$ClassName,
2143        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 2)]
2144        [ValidateNotNullorEmpty()]
2145        [PSCustomObject]$Qualifier = @()
2146    )
2147
2148    Begin {
2149        ## Get the name of this function and write header
2150        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
2151        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
2152    }
2153    Process {
2154        Try {
2155
2156            ## Check if the class exist
2157            $null = Get-WmiClass -Namespace $Namespace -ClassName $ClassName -ErrorAction 'Stop'
2158
2159            ## If input qualifier is not a hashtable convert string input to hashtable
2160            If ($Qualifier -isnot [hashtable]) {
2161                $Qualifier = $Qualifier | ConvertFrom-StringData
2162            }
2163
2164            ## Add the missing qualifier value, name and flavor to the hashtable using splatting
2165            If (-not $Qualifier.Item('Name')) { $Qualifier.Add('Name', 'Static') }
2166            If (-not $Qualifier.Item('Value')) { $Qualifier.Add('Value', $true) }
2167            If (-not $Qualifier.Item('IsAmended')) { $Qualifier.Add('IsAmended', $false) }
2168            If (-not $Qualifier.Item('PropagatesToInstance')) { $Qualifier.Add('PropagatesToInstance', $true) }
2169            If (-not $Qualifier.Item('PropagatesToSubClass')) { $Qualifier.Add('PropagatesToSubClass', $false) }
2170            If (-not $Qualifier.Item('IsOverridable')) { $Qualifier.Add('IsOverridable', $true) }
2171
2172            ## Create the ManagementClass object
2173            [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$ClassName")
2174
2175            ## Set key qualifier if specified, otherwise set qualifier
2176            $ClassObject.Qualifiers.Add($Qualifier.Item('Name'), $Qualifier.Item('Value'), $Qualifier.Item('IsAmended'), $Qualifier.Item('PropagatesToInstance'), $Qualifier.Item('PropagatesToSubClass'), $Qualifier.Item('IsOverridable'))
2177            $SetClassQualifiers = $ClassObject.Put()
2178            $ClassObject.Dispose()
2179
2180            ## On class qualifiers creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
2181            If (-not $SetClassQualifiers) {
2182
2183                #  Error handling and logging
2184                $SetClassQualifiersErr = "Failed to set qualifier [$Qualifier.Item('Name')] for class [$Namespace`:$ClassName]."
2185                Write-Log -Message $SetClassQualifiersErr -Severity 3 -Source ${CmdletName} -DebugMessage
2186                Write-Error -Message $SetClassQualifiersErr -Category 'InvalidResult'
2187            }
2188        }
2189        Catch {
2190            Write-Log -Message "Failed to set qualifier for class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
2191            Break
2192        }
2193        Finally {
2194            Write-Output -InputObject $SetClassQualifiers
2195        }
2196    }
2197    End {
2198        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
2199    }
2200}
2201#endregion
2202
2203#region Function Set-WmiPropertyQualifier
2204Function Set-WmiPropertyQualifier {
2205<#
2206.SYNOPSIS
2207    This function is used to set WMI property qualifier value.
2208.DESCRIPTION
2209    This function is used to set WMI property qualifier value to an existing WMI property.
2210.PARAMETER Namespace
2211    Specifies the namespace where to search for the WMI namespace. Default is: 'ROOT\cimv2'.
2212.PARAMETER ClassName
2213    Specifies the class name for which to add the properties.
2214.PARAMETER PropertyName
2215    Specifies the property name.
2216.PARAMETER Qualifier
2217    Specifies the qualifier name, value and flavours as hashtable. You can omit this parameter or enter one or more items in the hashtable.
2218    You can also specify a string but you must separate the name and value with a new line character (`n). This parameter can also be piped.
2219    If you omit a hashtable item the default item value will be used. Only item values can be specified (right of the '=' sign).
2220    Default is:
2221        [hashtable][ordered]@{
2222            Name = 'Static'
2223            Value = $true
2224            IsAmended = $false
2225            PropagatesToInstance = $true
2226            PropagatesToSubClass = $false
2227            IsOverridable = $true
2228        }
2229    Specifies if the property is key. Default is: $false.
2230.EXAMPLE
2231    Set-WmiPropertyQualifier -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -Property 'WebSite' -Qualifier @{ Name = 'Description' ; Value = 'SCCMZone Blog' }
2232.EXAMPLE
2233    Set-WmiPropertyQualifier -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -Property 'WebSite' -Qualifier "Name = Description `n Value = SCCMZone Blog"
2234.EXAMPLE
2235    "Name = Description `n Value = SCCMZone Blog" | Set-WmiPropertyQualifier -Namespace 'ROOT\SCCM' -ClassName 'SCCMZone' -Property 'WebSite'
2236.NOTES
2237    This is a module function and can typically be called directly.
2238.LINK
2239    https://MEMZ.one/PsWmiToolkit
2240.LINK
2241    https://MEMZ.one/PsWmiToolkit-GIT
2242.LINK
2243    https://MEMZ.one/PsWmiToolkit-ISSUES
2244#>
2245    [CmdletBinding()]
2246    Param (
2247        [Parameter(Mandatory = $false, Position = 0)]
2248        [ValidateNotNullorEmpty()]
2249        [string]$Namespace = 'ROOT\cimv2',
2250        [Parameter(Mandatory = $true, Position = 1)]
2251        [ValidateNotNullorEmpty()]
2252        [string]$ClassName,
2253        [Parameter(Mandatory = $true, Position = 2)]
2254        [ValidateNotNullorEmpty()]
2255        [string]$PropertyName,
2256        [Parameter(Mandatory = $false, ValueFromPipeline, Position = 3)]
2257        [ValidateNotNullorEmpty()]
2258        [PSCustomObject]$Qualifier = @()
2259    )
2260
2261    Begin {
2262        ## Get the name of this function and write header
2263        [string]${CmdletName} = $PSCmdlet.MyInvocation.MyCommand.Name
2264        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -CmdletBoundParameters $PSBoundParameters -Header
2265    }
2266    Process {
2267        Try {
2268
2269            ## Check if the property exists
2270            $null = Get-WmiProperty -Namespace $Namespace -ClassName $ClassName -PropertyName $PropertyName -ErrorAction 'Stop'
2271
2272            ## If input qualifier is not a hashtable convert string input to hashtable
2273            If ($Qualifier -isnot [hashtable]) {
2274                $Qualifier = $Qualifier | ConvertFrom-StringData
2275            }
2276
2277            ## Add the missing qualifier value, name and flavor to the hashtable using splatting
2278            If (-not $Qualifier.Item('Name')) { $Qualifier.Add('Name', 'Static') }
2279            If (-not $Qualifier.Item('Value')) { $Qualifier.Add('Value', $true) }
2280            If (-not $Qualifier.Item('IsAmended')) { $Qualifier.Add('IsAmended', $false) }
2281            If (-not $Qualifier.Item('PropagatesToInstance')) { $Qualifier.Add('PropagatesToInstance', $true) }
2282            If (-not $Qualifier.Item('PropagatesToSubClass')) { $Qualifier.Add('PropagatesToSubClass', $false) }
2283            If (-not $Qualifier.Item('IsOverridable')) { $Qualifier.Add('IsOverridable', $true) }
2284
2285            ## Create the ManagementClass object
2286            [wmiclass]$ClassObject = New-Object -TypeName 'System.Management.ManagementClass' -ArgumentList @("\\.\$Namespace`:$ClassName")
2287
2288            ## Set key qualifier if specified, otherwise set qualifier
2289            If ('key' -eq $Qualifier.Item('Name')) {
2290                $ClassObject.Properties[$PropertyName].Qualifiers.Add('Key', $true)
2291                $SetClassQualifiers = $ClassObject.Put()
2292                $ClassObject.Dispose()
2293            }
2294            Else {
2295                $ClassObject.Properties[$PropertyName].Qualifiers.Add($Qualifier.Item('Name'), $Qualifier.Item('Value'), $Qualifier.Item('IsAmended'), $Qualifier.Item('PropagatesToInstance'), $Qualifier.Item('PropagatesToSubClass'), $Qualifier.Item('IsOverridable'))
2296                $SetClassQualifiers = $ClassObject.Put()
2297                $ClassObject.Dispose()
2298            }
2299
2300            ## On property qualifiers creation failure, write debug message and optionally throw error if -ErrorAction 'Stop' is specified
2301            If (-not $SetClassQualifiers) {
2302
2303                #  Error handling and logging
2304                $SetClassQualifiersErr = "Failed to set qualifier [$Qualifier.Item('Name')] for property [$Namespace`:$ClassName($PropertyName)]."
2305                Write-Log -Message $SetClassQualifiersErr -Severity 3 -Source ${CmdletName} -DebugMessage
2306                Write-Error -Message $SetClassQualifiersErr -Category 'InvalidResult'
2307            }
2308        }
2309        Catch {
2310            Write-Log -Message "Failed to set property qualifier for class [$Namespace`:$ClassName]. `n$(Resolve-Error)" -Severity 3 -Source ${CmdletName}
2311        }
2312        Finally {
2313            Write-Output -InputObject $SetClassQualifiers
2314        }
2315    }
2316    End {
2317        Write-FunctionHeaderOrFooter -CmdletName ${CmdletName} -Footer
2318    }
2319}
2320#endregion
2321
2322#endregion
2323##*=============================================
2324##* END MODULE DEFINITION
2325##*=============================================
2326
2327#endregion
2328##*=============================================
2329##* END FUNCTION LISTINGS
2330##*=============================================
2331
2332##*=============================================
2333##* SCRIPT BODY
2334##*=============================================
2335#region ScriptBody
2336
2337## Get User Rights Assignment
2338$UserRightsAssignments  = Get-UserRightsAssignment
2339
2340## Remove existing class
2341Remove-WmiClass -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -ErrorAction 'SilentlyContinue'
2342
2343## Create new class
2344[hashtable]$Qualifiers = @{
2345    Static      = $true
2346    Description = 'Custom Configuration Manager Hardware Inventory Class for User Rights Assignment Data. Endpoint Management Team.'
2347}
2348New-WmiClass -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -Qualifiers $Qualifiers
2349
2350## Add class properties
2351New-WmiProperty -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -PropertyName 'PrincipalSID'     -PropertyType 'String' -Qualifiers @{ Key = $true }
2352New-WmiProperty -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -PropertyName 'PrincipalName'    -PropertyType 'String'
2353New-WmiProperty -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -PropertyName 'Privilege'        -PropertyType 'StringArray'
2354New-WmiProperty -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -PropertyName 'PrivilegeBitMask' -PropertyType 'UInt64' -Qualifiers @{ Key = $true }
2355
2356## Add class instances
2357ForEach ($UserRightsAssignment in $UserRightsAssignments) {
2358    #  Initialize loop variables
2359    [uint64]$Bitmask = 0
2360    [string[]]$Privileges = $UserRightsAssignment.Privilege
2361    ForEach ($Privilege in $Privileges) {
2362        #  Create bitmask
2363        [UInt64]$Bitmask += [UInt64][UserRightsFlags]::$Privilege
2364    }
2365    #  Convert PSCustomObject to Hashtable
2366    [hashtable]$Property = $UserRightsAssignment | ConvertTo-HashtableFromPsCustomObject
2367    #  Add bitmask to hashtable
2368    $Property.Add('PrivilegeBitMask',  [UInt64]$Bitmask)
2369    #  Write class instance
2370    New-WmiInstance -Namespace 'ROOT\CIMV2' -ClassName 'Win32_UserRightsAssignment' -Property $Property
2371}
2372
2373## Write output
2374Write-Output -InputObject $Output
2375
2376#endregion
2377##*=============================================
2378##* END SCRIPT BODY
2379##*=============================================

SHARE

article card image dark article card image light

Published by · May 22, 2024 tools · 1 mins read

Introducing: Windows User Rights Assignment Tool - Part 1

Get Windows Rights Assignment with our PowerShell Tool. ...

See More
article card image dark article card image light

Published by · Jun 18, 2023 tools · 2 mins read

Introducing: Windows Cache Cleanup Tool

Cleaning Windows and Configuration Manager Caches for Configuration Manager Build and Capture Task Sequence or Standalone Use ...

See More