Omnicit.psm1
class CanonicalName { hidden [string]$_FullName hidden [string]$_OrganizationalUnit hidden [string]$_Domain [string]$CanonicalName CanonicalName() { $this | Add-Member -Name FullName -MemberType ScriptProperty -Value { return $this._FullName } -SecondValue { param($Value) $this._FullName = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } $this | Add-Member -Name OrganizationalUnit -MemberType ScriptProperty -Value { return $this._OrganizationalUnit } -SecondValue { param($Value) $this._OrganizationalUnit = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } $this | Add-Member -Name Domain -MemberType ScriptProperty -Value { return $this._Domain } -SecondValue { param($Value) $this._Domain = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } } CanonicalName($CanonicalName) { if ($CanonicalName -match '^(?:(?!\/))(?:(?<Domain>[^\/]+)\/)?(?:(?<Path>.+(?=\/)+)\/)?(?:(?<Name>.+))?$') { $this._FullName = if ($Matches['Domain'] -eq [string]::Empty) { $null } else { $Matches['Name'] } $this._OrganizationalUnit = if ($Matches['Path']) { $Matches['Path'] } $this._Domain = if ($Matches['Domain']) { $Matches['Domain'] } else { $Matches['Name'] } $this.CanonicalName = $Matches[0] $this | Add-Member -Name FullName -MemberType ScriptProperty -Value { return $this._FullName } -SecondValue { param($Value) $this._FullName = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } $this | Add-Member -Name OrganizationalUnit -MemberType ScriptProperty -Value { return $this._OrganizationalUnit } -SecondValue { param($Value) $this._OrganizationalUnit = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } $this | Add-Member -Name Domain -MemberType ScriptProperty -Value { return $this._Domain } -SecondValue { param($Value) $this._Domain = $Value try { $this.BuildCanonicalName($this._Domain, $this._OrganizationalUnit, $this._FullName) } catch { Write-Verbose -Message 'Uncompleted FullName, OrganizationalUnit or Domain to finish build CanonicalName' } } } else { Write-Verbose -Message 'String does not contain a valid CanonicalName.' break } } hidden [void]BuildCanonicalName([string]$CN, [string]$OU, [string]$DN) { [string]$Slash = '/' if ($OU -eq [string]::Empty) { $Slash = $null } $this.CanonicalName = '{0}/{1}{2}{3}' -f $CN, $OU, $Slash, $DN } [string] ConvertToDistinguishedName () { if ($null -eq $this.CanonicalName) { Write-Verbose -Message 'No CanonicalName was defined.' break } [Text.StringBuilder]$DistinguishedName = [Text.StringBuilder]::new() [string[]]$String = $this.CanonicalName.Split('/') if ($this._FullName -ne [string]::Empty) { $null = $DistinguishedName.Append('CN={0}' -f ($String[$String.Count - 1])) } for ($i = $String.Count - 2; $i -ge 1; $i--) { $null = $DistinguishedName.Append(',OU={0}' -f ($String[$i])) } foreach ($Top in ($String[0].Split('.'))) { $null = $DistinguishedName.Append(',DC={0}' -f ($Top)) } return $DistinguishedName.ToString().Trim(',') } [string]ToString() { return $this.CanonicalName } } class CommonName { [ValidatePattern('^CN=[a-z1-9]')] [ValidateLength(4, 67)] [string]$Value CommonName([string]$CommonName) { $this.Value = $CommonName } [string]ToString() { return $this.Value } } class OrganizationalUnit { [ValidatePattern('^(?:((?:(?:OU)=[^,]+,?)+))')] [ValidateLength(4, 67)] [string]$Value OrganizationalUnit([string]$OrganizationalUnit) { $this.Value = $OrganizationalUnit } [string]ToString() { return $this.Value } } class Domain { [ValidatePattern('^((?:DC=[^,]+,?)+)')] [ValidateLength(4, 67)] [string]$Value Domain([string]$Domain) { $this.Value = $Domain } [string]ToString() { return $this.Value } } class DistinguishedName { hidden [string]$_FullName hidden [string]$_CommonName hidden [string]$_OrganizationalUnit hidden [string]$_Domain [string]$DistinguishedName DistinguishedName() { $this | Add-Member -Name FullName -MemberType ScriptProperty -Value { return [string]$this._FullName } -SecondValue { param($Value) $this._FullName = $Value $this._CommonName = 'CN={0}' -f $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name CommonName -MemberType ScriptProperty -Value { return [string]$this._CommonName } -SecondValue { param([CommonName]$Value) $this._FullName = ($Value -replace '^CN=') $this._CommonName = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name OrganizationalUnit -MemberType ScriptProperty -Value { return [string]$this._OrganizationalUnit } -SecondValue { param([OrganizationalUnit]$Value) $this._OrganizationalUnit = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name Domain -MemberType ScriptProperty -Value { return [string]$this._Domain } -SecondValue { param([Domain]$Value) $this._Domain = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } } DistinguishedName([string]$DistinguishedName) { if ($DistinguishedName -match '^(?:(?<CN>CN=(?<Name>[^,]*)),)?(?:(?<OU>(?:(?:OU)=[^,]+,?)+),)?(?<Domain>(?:DC=[^,]+,?)+)$') { $this._FullName = $Matches['Name'] $this._CommonName = 'CN={0}' -f $Matches['Name'] $this._OrganizationalUnit = if ($Matches['OU']) { $Matches['OU'] } $this._Domain = $Matches['Domain'] $this.DistinguishedName = $Matches[0] $this | Add-Member -Name FullName -MemberType ScriptProperty -Value { return $this._FullName } -SecondValue { param($Value) $this._FullName = $Value $this._CommonName = 'CN={0}' -f $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name CommonName -MemberType ScriptProperty -Value { return [string]$this._CommonName } -SecondValue { param([CommonName]$Value) $this._FullName = ($Value -replace '^CN=') $this._CommonName = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name OrganizationalUnit -MemberType ScriptProperty -Value { return $this._OrganizationalUnit } -SecondValue { param([OrganizationalUnit]$Value) $this._OrganizationalUnit = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name Domain -MemberType ScriptProperty -Value { return $this._Domain } -SecondValue { param([Domain]$Value) $this._Domain = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build the DistinguishedName string' } } } else { Write-Verbose -Message 'String does not contain a valid DistinguishedName.' break } } DistinguishedName([string]$CommonName, [string]$OrganizationalUnit, [string]$Domain) { try { $this.BuildDistinguishedName($CommonName, $OrganizationalUnit, $Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build the DistinguishedName string' break } $this._CommonName = $CommonName $this._FullName = ($CommonName -replace '^CN=') $this._OrganizationalUnit = $OrganizationalUnit $this._Domain = $Domain $this | Add-Member -Name FullName -MemberType ScriptProperty -Value { return [string]$this._FullName } -SecondValue { param($Value) $this._FullName = $Value $this._CommonName = 'CN={0}' -f $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name CommonName -MemberType ScriptProperty -Value { return [string]$this._CommonName } -SecondValue { param([CommonName]$Value) $this._FullName = ($Value -replace '^CN=') $this._CommonName = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name OrganizationalUnit -MemberType ScriptProperty -Value { return [string]$this._OrganizationalUnit } -SecondValue { param([OrganizationalUnit]$Value) $this._OrganizationalUnit = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } $this | Add-Member -Name Domain -MemberType ScriptProperty -Value { return [string]$this._Domain } -SecondValue { param([Domain]$Value) $this._Domain = $Value try { $this.BuildDistinguishedName($this._CommonName, $this._OrganizationalUnit, $this._Domain) } catch { Write-Verbose -Message 'Uncompleted CommonName, OrganizationalUnit or Domain to finish build DistinguishedName' } } } hidden [void]BuildDistinguishedName([CommonName]$CN, [OrganizationalUnit]$OU, [Domain]$DN) { $this.DistinguishedName = '{0},{1},{2}' -f $CN.ToString(), $OU.ToString(), $DN.ToString() } [string] ConvertToCanonicalName () { if ($null -eq $this.DistinguishedName) { Write-Verbose -Message 'No DistinguishedName was defined.' break } [string]$CN = $null [Collections.ArrayList]$OU = [Collections.ArrayList]::new() [Text.StringBuilder]$Canonical = [Text.StringBuilder]::new() [Text.StringBuilder]$DC = [Text.StringBuilder]::new() [string[]]$String = ($this.DistinguishedName) -split '(?<!\\),(?!\,)' foreach ($SubString in $String) { $SubString = $SubString.TrimStart() # Remove or replace each Distinguished Name indicator with a corresponding trailing char '/' for CanonicalName. switch ($SubString.SubString(0, 3)) { 'CN=' { [string]$CN = '{0}' -f ($SubString -replace 'CN=') continue } 'OU=' { $null = $OU.Add('{0}/' -f ($SubString -replace 'OU=')) continue } 'DC=' { $null = $DC.Append('{0}.' -f ($SubString -replace 'DC=')) continue } } } $OU.Reverse() [Text.StringBuilder]$Canonical.Append([string]($DC -replace '\.$', '/')) [Text.StringBuilder]$Canonical.Append(-join $OU) [Text.StringBuilder]$Canonical.Append($CN) return $Canonical.ToString().TrimEnd('/') } [string]ToString() { return $this.DistinguishedName } } class NormalizeString { hidden [Text.Encoding]$Encoding = [Text.Encoding]::GetEncoding('ISO-8859-6') hidden [string]$_NormalizedString hidden [string]$_String NormalizeString() { $this | Add-Member -Name String -MemberType ScriptProperty -Value { return $this._String } -SecondValue { param($Value) $this._String = $Value $this.FormatString() } $this | Add-Member -Name NormalizedString -MemberType ScriptProperty -Value { return $this._NormalizedString } } NormalizeString([string]$Value) { $this | Add-Member -Name String -MemberType ScriptProperty -Value { return $this._String } -SecondValue { param($Value) $this._String = $Value $this.FormatString() } $this | Add-Member -Name NormalizedString -MemberType ScriptProperty -Value { return $this._NormalizedString } $this.String = $Value } NormalizeString([string]$Value, [Text.Encoding]$Encoding) { $this | Add-Member -Name String -MemberType ScriptProperty -Value { return $this._String } -SecondValue { param($Value) $this._String = $Value $this.FormatString() } $this | Add-Member -Name NormalizedString -MemberType ScriptProperty -Value { return $this._NormalizedString } $this.String = $Value $this.Encoding = [Text.Encoding]::GetEncoding($Encoding) } hidden [string]FormatString() { try { $StringBuilder = [Text.StringBuilder]::new() $Bytes = [Text.Encoding]::Convert([Text.Encoding]::Unicode, $this.Encoding, [Text.Encoding]::Unicode.GetBytes($this._String)) [string]$ConvertedString = $this.Encoding.GetString($Bytes) foreach ($Char in $ConvertedString.Normalize([Text.NormalizationForm]::FormD).GetEnumerator()) { if ([Globalization.CharUnicodeInfo]::GetUnicodeCategory($Char) -ne [Globalization.UnicodeCategory]::NonSpacingMark) { $null = $StringBuilder.Append($Char) } } $this._NormalizedString = $StringBuilder.ToString() return $this._NormalizedString } catch { Write-Verbose -Message ('Unable to convert string - {0}' -f $_.Exception.Message) break } } [string] ToString() { return $this.NormalizedString } } enum TypeAccelerators { DateTime Int32 Int64 Boolean Char Byte Decimal Double Int16 SByte Single UInt16 UInt32 UInt64 String All } class ConvertTypeAccelerator { [Object]$Object [Object]$ObjectType ConvertTypeAccelerator([string]$Object, [TypeAccelerators[]]$ObjectType, [Object]$PSTypeAccelerators) { if ($ObjectType -eq [TypeAccelerators]::All) { [TypeAccelerators[]]$Enums = ([enum]::GetNames([TypeAccelerators])).Where( { $_ -ne 'All' } ) } else { [TypeAccelerators[]]$Enums = $ObjectType } foreach ($Enum in $Enums) { try { $this.Object = [Convert]::"To$($Enum)"($Object) if ($Enum -eq 'Boolean') { $Enum = 'bool' } $this.ObjectType = $PSTypeAccelerators[$Enum.ToString()] break } catch { continue } } if ($null -eq $this.Object) { $this.Object = [string]$Object $this.ObjectType = $PSTypeAccelerators['String'] } } } function Get-PSTypeAccelerator { [CmdletBinding()] param() try { [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Get } catch { $PSCmdlet.ThrowTerminatingError($_) } } function Compare-ObjectProperty { <# .SYNOPSIS The Compare-ObjectProperty function compares two sets of objects and it's properties. .DESCRIPTION The Compare-ObjectProperty function compares two sets of objects and it's properties. One set of objects is the "reference set" and the other set is the "difference set." The result will indicate if each property from the reference set is equal to each property of the difference set. .EXAMPLE $Ref = Get-ADUser -Identity 'Roger Johnsson' -Properties * $Dif = Get-ADUser -Identity 'John Rogersson' -Properties * Compare-ObjectProperty -ReferenceObject $Ref -DifferenceObject $Dif This example returns all properties for both the reference object and the difference object with a true respectively false if the properties is equal. The original property for both reference object and the difference object is also returned. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Compare-ObjectProperty.md #> [CmdletBinding( PositionalBinding, SupportsShouldProcess )] [Alias('cop')] param( # Specifies an object used as a reference for comparison. [Parameter( HelpMessage = 'Enter objects used as a reference for comparison.', ValueFromPipeline, Mandatory, Position = 0 )] [ValidateNotNullOrEmpty()] [PSObject]$ReferenceObject, # Specifies the object that are compared to the reference objects. [Parameter( HelpMessage = 'Enter the objects that are compared to the reference objects.', Mandatory, Position = 1 )] [ValidateNotNullOrEmpty()] [PSObject]$DifferenceObject ) $Properties = [Collections.ArrayList]::new() $null = $Properties.AddRange([string[]]($ReferenceObject | Get-Member -MemberType Properties).Name) $null = $Properties.AddRange([string[]]($DifferenceObject | Get-Member -MemberType Properties).Name) $AllProperties = $Properties | Sort-Object -Unique foreach ($Property in $AllProperties) { if ($PSCmdlet.ShouldProcess($Property, $MyInvocation.MyCommand.Name)) { try { $DiffProperty = Compare-Object -ReferenceObject $ReferenceObject -DifferenceObject $DifferenceObject -Property $Property -ErrorAction Stop } catch { $DiffProperty = $true } [PSCustomObject]@{ PSTypeName = 'Omnicit.Compare.ObjectProperty' PropertyName = $Property PropertyMatch = (-not [bool]$DiffProperty) ReferenceValue = $ReferenceObject."$Property" DifferenceValue = $DifferenceObject."$Property" } } } } function ConvertFrom-CanonicalName { <# .SYNOPSIS Convert a CanonicalName string to a DistinguishedName string. .DESCRIPTION The ConvertFrom-CanonicalName converts one or more strings in form of a CanonicalName into DistinguishedName strings. Common usage when working in Exchange and comparing objects in Active Directory. .EXAMPLE ConvertFrom-CanonicalName -CanonicalName 'Contoso.com/Department/Users/Roger Johnsson' CN=Roger Johnsson,OU=Users,OU=Department,DC=Contoso,DC=com This example returns the converted CanonicalName in form of a DistinguishedName. .EXAMPLE ConvertFrom-CanonicalName -CanonicalName 'Contoso.com/Department/Users' -OrganizationalUnit OU=Users,OU=Department,DC=Contoso,DC=com This example returns the converted CanonicalName in form of a DistinguishedName. In this case the last object after slash '/' is an OrganizationalUnit. .EXAMPLE ConvertFrom-CanonicalName -CanonicalName 'Contoso.com/Department/Users/Roger Johnsson', 'Contoso.com/Tier1/Users/Bill T Admin' CN=Roger Johnsson,OU=Users,OU=Department,DC=Contoso,DC=com CN=Bill T Admin,OU=Users,OU=Tier1,DC=Contoso,DC=com This example returns each CanonicalName converted in form of a DistinguishedName. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/ConvertFrom-CanonicalName.md #> [OutputType([DistinguishedName])] [CmdletBinding( SupportsShouldProcess )] param ( # Specifies the CanonicalName string to be converted to a DistinguishedName string. [Parameter( Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Input a valid CanonicalName. Example: "Contoso.com/Department/Users/Roger Johnsson"' )] [ValidateNotNullOrEmpty()] [Alias('CN')] [CanonicalName[]]$CanonicalName, # Specifies that the object is an OrganizationalUnit (OU=) instead of an Person (CN=). # Will automatically be an OrganizationalUnit if the CanonicalName string ends with a slash '/' [switch]$OrganizationalUnit ) process { foreach ($Name in $CanonicalName) { if ($PSCmdlet.ShouldProcess(('{0}' -f $Name, $MyInvocation.MyCommand.Name))) { if ($PSBoundParameters.ContainsKey('OrganizationalUnit') -and $Name.CanonicalName -notmatch '\/$') { $Name.OrganizationalUnit = '{0}/{0}' -f $Name.OrganizationalUnit, $Name.FullName $Name.FullName = $null } [DistinguishedName]$Name.ConvertToDistinguishedName() } } } } function ConvertFrom-DistinguishedName { <# .SYNOPSIS Convert a DistinguishedName string to a CanonicalName string. .DESCRIPTION The ConvertFrom-DistinguishedName converts one or more strings in form of a DistinguishedName into CanonicalName strings. Common usage when working in Exchange and comparing objects in Active Directory. .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'CN=Roger Johnsson,OU=Users,OU=Department,DC=Contoso,DC=com' Contoso.com/Department/Users/Roger Johnsson This example returns the converted DistinguishedName in form of a CanonicalName. .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'CN=Roger Johnsson,OU=Users,OU=Department,DC=Contoso,DC=com', 'CN=Bill T Admin,OU=Users,OU=Tier1,DC=Contoso,DC=com' Contoso.com/Department/Users/Roger Johnsson Contoso.com/Tier1/Users/Bill T Admin This example returns each DistinguishedName converted in form of a CanonicalName. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/ConvertFrom-DistinguishedName.md #> [OutputType([CanonicalName])] [CmdletBinding( SupportsShouldProcess )] param ( # Specifies one or more Distinguished Names to be converted to Canonical Names. [Parameter( Mandatory, ValueFromPipeline, HelpMessage = 'Input a valid DistinguishedName. Example: "CN=Roger Johnsson,OU=Users,OU=Department,DC=Contoso,DC=com"' )] [ValidateNotNullOrEmpty()] [Alias('DN')] [DistinguishedName[]]$DistinguishedName ) process { foreach ($Name in $DistinguishedName) { if ($PSCmdlet.ShouldProcess(('{0}' -f $Name, $MyInvocation.MyCommand.Name))) { [CanonicalName]$Name.ConvertToCanonicalName() } } } } function ConvertTo-MailNormalization { <# .SYNOPSIS Convert a string for Mail Normalization. .DESCRIPTION Converts accented, diacritics and most European chars to the corresponding a-z char. Effectively and fast converts a string to a normalized mail string using [Text.NormalizationForm]::FormD and [Text.Encoding]::GetEncoding('ISO-8859-8'). Function created to support legacy email providers that does not support e-mail address internationalization (EAI). Integers will remain intact. .EXAMPLE ConvertTo-MailNormalization -InputObject 'ûüåäöÅÄÖÆÈÉÊËÐÑØçł' uuaaoAAOAEEEEDNOcl This example returns a string with the converted Mail Normalization value. .NOTES Credits to Johan Åkerlund for the Convert-DiacriticCharacters function. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/ConvertTo-MailNormalization.md #> [OutputType([System.String])] [CmdletBinding( SupportsShouldProcess )] param ( # Specifies the string to be converted to Mail Normalization. [Parameter( Mandatory, ValueFromPipeline, HelpMessage = 'Specify a string to be converted to Unicode Normalization.' )] [AllowEmptyString()] [Alias('String')] [string]$InputObject ) process { foreach ($String in $InputObject) { if ($PSCmdlet.ShouldProcess(('{0}' -f $String, $MyInvocation.MyCommand.Name))) { try { [NormalizeString]::new($String).ToString() } catch { $PSCmdlet.ThrowTerminatingError($_) } } } } } function ConvertTo-TitleCase { <# .SYNOPSIS The ConvertTo-TitleCase function converts a specified string to title case. .DESCRIPTION The ConvertTo-TitleCase function converts a specified string to title case using the Method in (Get-Culture).TextInfo All input strings will be converted to lowercase, because uppercase are considered to be acronyms. .EXAMPLE ConvertTo-TitleCase -InputObject 'roger johnsson' Roger Johnsson This example returns the string 'Roger Johnsson' which has capitalized the R and J chars. .EXAMPLE 'roger johnsson', 'JOHN ROGERSSON' | ConvertTo-TitleCase Roger Johnsson John Rogersson This example returns the strings 'Roger Johnsson' and 'John Rogersson' which has capitalized the R and J chars. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/ConvertTo-TitleCase.md #> [Alias('ConvertTo-NameTitle')] [CmdletBinding( PositionalBinding, SupportsShouldProcess )] param ( # Specifies one or more objects to be convert to title case strings. [Parameter( Mandatory, ValueFromPipeline, Position = 0 )] [AllowEmptyString()] [string[]]$InputObject ) begin { $TextInfo = (Get-Culture).TextInfo } process { foreach ($String in $InputObject) { if ($PSCmdlet.ShouldProcess($String)) { $TextInfo.ToTitleCase($String.ToLower()) } } } } function Get-ClipboardArray { <# .SYNOPSIS Paste an array of objects from the clipboard (CTRL+V) .DESCRIPTION Paste an array of objects from the clipboard (CTRL+V) to the variable $ClipboardArray (global scope). Using the parameter, AsType, can specify what every object should be tried to be converted to. If conversion fails every object as a type string. The order for the data type conversion is: [DateTime], [Int32], [Int64], [Boolean], [Char], [Byte], [Decimal], [Double], [Int16], [SByte], [Single], [UInt16], [UInt32], [UInt64], [String] Default an empty string will close the loop from importing any more data, default behavior of Read-Host. To change that behavior a specified [System.String] combination can be used with the -BreakString parameter to include empty lines as valid input data. .EXAMPLE Get-ClipboardArray No.[0]: Some data No.[1]: to No.[2]: paste No.[3]: into No.[4]: the function No.[5]: Empty line will close the loop from importing anything more. The input to will be saved to the variable ClipboardArray. Each item in the array will be of the data type [System.String]. .EXAMPLE Get-ClipboardArray -AsType Int32 No.[0]: Some data No.[1]: 20170101 No.[2]: 123 No.[3]: into No.[4]: 876 function No.[5]: Empty line will close the loop from importing anything more. The input to will be saved to the variable ClipboardArray. This example will try to convert every object in the pasted array to an System.Int32. If its not possible it will default back to System.String. Array object [1] and [2] is the only objects that will be converted to a System.Int32 type accelerator. .EXAMPLE Get-ClipboardArray -AsType All No.[0]: Some data No.[1]: 2017-01-01 No.[2]: 2147483647 No.[3]: 2147483648 No.[4]: TRUE No.[5]: Empty line will close the loop from importing anything more. The input to will be saved to the variable ClipboardArray. This example will try to convert every object in the pasted array to all available data types. If no conversion succeeded it will default back to System.String. The order for the data type conversion is [System.DateTime], [System.Int32], [System.Int64], [System.Boolean], [System.String]. Array object [0] is a String, object [1] is a DateTime, object [2] is an Int32, object [3] is an Int64 and object [4] is a Boolean. .EXAMPLE Get-ClipboardArray -BreakString '?' No.[0]: Some data No.[1]: 2017-01-01 No.[2]: 2147483647 No.[3]: 2147483648 No.[4]: TRUE No.[5]: No.[6]: ? The question mark will close the loop from importing anything more. The input to will be saved to the variable ClipboardArray. This example will not convert any objects in the array, all objects default to System.String. The BreakString parameter can be used when the input contains empty rows to not break the loop. .EXAMPLE Get-ClipboardArray -BreakString '?' -AsType All No.[0]: Some data No.[1]: 2017-01-01 No.[2]: 2147483647 No.[3]: 2147483648 No.[4]: TRUE No.[5]: No.[6]: ? $ClipboardArray | foreach {$_.Gettype()} IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True String System.Object True True DateTime System.ValueType True True Int32 System.ValueType True True Int64 System.ValueType True True Boolean System.ValueType True True String System.Object The exclamation mark will close the loop from importing anything more. The input to will be saved to the variable ClipboardArray. This example will try to convert every object in the array to all available data types. If no conversion succeeded it will default back to System.String. The order for the data type conversion is [System.DateTime], [System.Int32], [System.Int64], [System.Boolean], [System.String]. Array object [0] is a String, object [1] is a DateTime, object [2] is an Int32, object [3] is an Int64, object [4] is a Boolean and object [5] is a String. The BreakString parameter can be used when the input contains empty rows to not break the loop. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Get-ClipboardArray.md #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] [CmdletBinding( PositionalBinding )] [Alias('gca')] param ( <# Specifies the a single char or a string that will break the input loop. Default value is empty string '' Quote and some other special chars ("''`´“”‘‘’’„!) is not permitted as input. #> [Parameter( Position = 0 )] [Alias('B')] [AllowEmptyString()] [ValidatePattern('[^"''`´“”‘‘’’„!]')] [string]$BreakString = '', <# Specifies the TypeAccelerator of the clipboard that each input will be converted to. The acceptable values for this parameter are: - DateTime - Int32 - Int64 - Boolean - Char - Byte - Decimal - Double - Int16 - SByte - Single - UInt16 - UInt32 - UInt64 - String - All #> [Parameter( Position = 1 )] [ValidateSet('DateTime', 'Int32', 'Int64', 'Boolean', 'Char', 'Byte', 'Decimal', 'Double', 'Int16', 'SByte', 'Single', 'UInt16', 'UInt32', 'UInt64', 'String', 'All')] [Alias('D')] [TypeAccelerators[]]$AsType = 'String' ) try { $PSTypeAccelerator = Get-PSTypeAccelerator -ErrorAction Stop New-Variable -Name ClipboardArray -Value ([Collections.ArrayList]::new()) -Scope Global -Force -Description 'Variable created from Get-ClipboardArray.' -ErrorAction Stop [int32]$n = 0 } catch { $PSCmdlet.ThrowTerminatingError($_) } do { # Read-Host instead of Get-Clipboard for Cross platform compatibility $Input = (Read-Host -Prompt "No.[$($n)]") $n++ if ($Input -ne $BreakString) { $null = $global:ClipboardArray.Add([ConvertTypeAccelerator]::new($Input, $AsType, $PSTypeAccelerator)) } } until ($Input -eq $BreakString) Write-Verbose -Message 'Enter $ClipboardArray to access the pasted array objects' -Verbose } function Get-FolderSize { <# .SYNOPSIS Get folder size using Robocopy.exe and displays the output in a PSObject table. .DESCRIPTION This function uses Robocopy.exe to calculate the size used for the select path. You can specify if you want to display files older than a specific date using the MinFileData parameter. If you experience that the function Get-FolderSize (Robocopy) takes time to execute you can increase the threads used by Robocopy with the RobocopyThreadCount parameter. .EXAMPLE Get-FolderSize Path TotalBytes TotalMBytes TotalGBytes FilesCount DirCount TimeElapsed ---- ---------- ----------- ----------- ---------- -------- ----------- C:\Windows 18512797867 17655,18 17,24 144492 34036 00:00:12.2813478 This example returns a PSObject with the total folder size for the current location, which in this case in C:\Windows. .EXAMPLE Get-FolderSize -Path \\contoso.com\NETLOGON Path TotalBytes TotalMBytes TotalGBytes FilesCount DirCount TimeElapsed ---- ---------- ----------- ----------- ---------- -------- ----------- \\contoso.com\NETLOGON 1019904 0,97 0,00 1 2 00:00:00.0157300 This example returns a PSObject with the total folder size for the specified location path \\contoso.com\NETLOGON. .EXAMPLE Get-FolderSize -Path \\contoso.net\NETLOGON -BytePrecision 4 Path TotalBytes TotalMBytes TotalGBytes FilesCount DirCount TimeElapsed ---- ---------- ----------- ----------- ---------- -------- ----------- \\contoso.com\NETLOGON 1019904 0,9727 0,0009 1 2 00:00:00.0155598 This example returns a PSObject with the total folder size, with for decimals, for the specified location path \\contoso.com\NETLOGON. .EXAMPLE Get-FolderSize -Path C:\Windows -MinFileAgeDate 2019-06-01 Path TotalBytes TotalMBytes TotalGBytes FilesCount DirCount TimeElapsed ---- ---------- ----------- ----------- ---------- -------- ----------- C:\Windows 11448807458 10918,43 10,66 101354 34036 00:00:24.1594950 This example returns a PSObject with the total folder size for the specified location path C:\Windows and excludes files newer than 2019-06-01. .EXAMPLE Get-FolderSize -Path C:\Windows -MaxFileAgeDate 2019-06-01 Path TotalBytes TotalMBytes TotalGBytes FilesCount DirCount TimeElapsed ---- ---------- ----------- ----------- ---------- -------- ----------- C:\Windows 7063990409 6736,75 6,58 43138 34036 00:00:10.2498128 This example returns a PSObject with the total folder size for the specified location path C:\Windows and excludes files older than 2019-06-01. .EXAMPLE Get-FolderSize -Path C:\Windows -MaxFileAgeDate 2019-06-01 | Select-Object -Property * Path : C:\Windows TotalBytes : 7065038985 TotalMBytes : 6737,75 TotalGBytes : 6,58 FilesCount : 43138 DirCount : 34036 BytesFailed : 0 DirFailed : 0 FileFailed : 0 TimeElapsed : 00:00:10.0465934 StartedTime : 2019-09-05 22:28:33 EndedTime : 2019-09-05 22:28:43 DateFilter : Maximum file age "2019-06-01". TotalBytesNoDate : 18474790494 TotalMBytesNoDate : 17618,93 TotalGBytesNoDate : 17,21 DirCountNoDate : 34057 FileCountNoDate : 144414 This example returns a PSObject with all available properties extracted from the specified location path C:\Windows and excludes files older than 2019-06-01. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Get-FolderSize.md #> [CmdletBinding( SupportsShouldProcess, DefaultParameterSetName = 'Default', PositionalBinding )] param ( # Specifies the path to the source directory to measure. [Parameter( ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0 )] [PSDefaultValue(Help = 'Current path')] [string[]]$Path = ($PWD.Path), # Specifies the minimum file age (exclude files newer than N date). [Parameter( ParameterSetName = 'MinFile', Mandatory, Position = 1 )] [Alias('MinDate')] [datetime]$MinFileAgeDate, # Specifies the maximum file age (to exclude files older than N date). [Parameter( ParameterSetName = 'MaxFile', Mandatory, Position = 2 )] [Alias('MaxDate')] [datetime]$MaxFileAgeDate, # Specifies number of fractional digits, and rounds midpoint values to the nearest even number when converting bytes. [Parameter( Position = 3 )] [int]$BytePrecision = 2, # Number of threads used for Robocopy. [Parameter( Position = 4 )] [ValidateRange(1, 128)] [int]$RobocopyThreadCount = 16 ) begin { try { $PreviousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' if (-not $IsWindows -and $PSVersionTable.PSEdition -eq 'Core') { throw [System.NotSupportedException]::New('Get-FolderSize is only supported on Windows operating systems.') } if (-not (Get-Command -Name "$env:windir\system32\robocopy.exe" -ErrorAction SilentlyContinue)) { throw [System.NotSupportedException]::New("Unable to locate Robocopy.exe in $env:windir\system32. Please verify if Robocopy.exe is available in the specified path.") } } catch { $PSCmdlet.ThrowTerminatingError($_) } finally { $ErrorActionPreference = $PreviousErrorActionPreference } [Collections.ArrayList]$RobocopyArgs = @('/BYTES', '/FP', '/L', "/MT:$RobocopyThreadCount", '/NC', '/NDL', '/NJH', '/R:0', '/S', '/TS', '/W:0', '/XJ') $DateFilter = $null switch ($PSBoundParameters.Keys) { MinFileAgeDate { $null = $RobocopyArgs.Add("/MINAGE:$($MinFileAgeDate.ToString('yyyyMMdd'))") [string]$DateFilter = ('Minimum file age "{0}".' -f $MinFileAgeDate.ToShortDateString()) } MaxFileAgeDate { $null = $RobocopyArgs.Add("/MAXAGE:$($MaxFileAgeDate.ToString('yyyyMMdd'))") [string]$DateFilter = ('Maximum file age "{0}".' -f $MaxFileAgeDate.ToShortDateString()) } } # Thank you 'Joakim Svendsen' for the regular expression examples and inspiration! [regex]$HeaderRegex = '\s+Total\s+Copied\s+Skipped\s+Mismatch\s+FAILED\s+Extras' [regex]$DirLineRegex = 'Dirs\s+:\s+(?<DirCount>\d+)\s+(?<DirCopiedCount>\d+)(?:\s+\d+){2}\s+(?<DirsFailed>\d+)\s+\d+' [regex]$FileLineRegex = 'Files\s+:\s+(?<FileCount>\d+)\s+(?<FilesCopiedCount>\d+)(?:\s+\d+){2}\s+(?<FilesFailed>\d+)\s+\d+' [regex]$BytesLineRegex = 'Bytes\s+:\s+(?<ByteCount>\d+)\s+(?<ByteCopiedCount>\d+)(?:\s+\d+){2}\s+(?<BytesFailed>\d+)\s+\d+' [regex]$TimeLineRegex = 'Times\s+:\s+(?<TimeElapsed>\d+:\d+:\d+).+' [regex]$EndedLineRegex = 'Ended\s+:\s+(?<EndedTime>.+)' } process { foreach ($InlinePath in $Path) { if ($PSCmdlet.ShouldProcess($InlinePath)) { try { [datetime]$StartTime = [datetime]::Now [string]$Summary = (& "$env:windir\system32\robocopy.exe" $InlinePath NULL $RobocopyArgs)[-8..-1] [datetime]$EndTime = [datetime]::Now if ($Summary -match "$HeaderRegex\s+$DirLineRegex\s+$FileLineRegex\s+$BytesLineRegex\s+$TimeLineRegex\s+$EndedLineRegex") { [PSCustomObject]@{ PSTypeName = 'Omnicit.Get.FolderSize' Path = [string]$InlinePath TotalBytes = [decimal]$Matches['ByteCopiedCount'] TotalMBytes = [decimal]([math]::Round(([decimal]$Matches['ByteCopiedCount'] / 1MB), $BytePrecision)) TotalGBytes = [decimal]([math]::Round(([decimal]$Matches['ByteCopiedCount'] / 1GB), $BytePrecision)) FilesCount = [decimal]$Matches['FilesCopiedCount'] DirCount = [decimal]$Matches['DirCopiedCount'] BytesFailed = [decimal]$Matches['BytesFailed'] DirFailed = [decimal]$Matches['DirFailed'] FileFailed = [decimal]$Matches['FileFailed'] TimeElapsed = [TimeSpan]($EndTime - $StartTime) StartedTime = [datetime]$StartTime EndedTime = [datetime]$EndTime DateFilter = [string]$DateFilter TotalBytesNoDate = [decimal]$Matches['ByteCount'] TotalMBytesNoDate = [decimal]([math]::Round(([decimal]$Matches['ByteCount'] / 1MB), $BytePrecision)) TotalGBytesNoDate = [decimal]([math]::Round(([decimal]$Matches['ByteCount'] / 1GB), $BytePrecision)) DirCountNoDate = [decimal]$Matches['DirCount'] FileCountNoDate = [decimal]$Matches['FileCount'] } } else { Write-Warning -Message "Unexpected format returned from Robocopy.exe for path '$InlinePath'." -WarningAction Continue } } catch { $PSCmdlet.ThrowTerminatingError($_) } } } } } function Get-Parameter { <# .SYNOPSIS Enumerates the parameters of one or more commands .DESCRIPTION Lists all the parameters of a command, by ParameterSet, including their aliases, type, position, mandatory, etc. By default, formats the output to tables grouped by command and parameter set. .EXAMPLE Get-Parameter -CommandName Select-XML Command: Microsoft.PowerShell.Utility/Select-Xml Set: Xml * Name Aliases Position Mandatory Pipeline ByName Provider Type ---- ------- -------- --------- -------- ------ -------- ---- Namespace {Na*} Named False False False All Hashtable Xml {Node, Xm*} 1 True True True All XmlNode[] XPath {XP*} 0 True False False All String Command: Microsoft.PowerShell.Utility/Select-Xml Set: Path Name Aliases Position Mandatory Pipeline ByName Provider Type ---- ------- -------- --------- -------- ------ -------- ---- Namespace {Na*} Named False False False All Hashtable Path {Pa*} 1 True False True All String[] XPath {XP*} 0 True False False All String ... This example returns a PSObject which displays the parameters sorted by the Parameter Sets for the cmdlet Select-XML. .EXAMPLE Get-Command Select-Xml | Get-Parameter Command: Microsoft.PowerShell.Utility/Select-Xml Set: Xml * Name Aliases Position Mandatory Pipeline ByName Provider Type ---- ------- -------- --------- -------- ------ -------- ---- Namespace {Na*} Named False False False All Hashtable Xml {Node, Xm*} 1 True True True All XmlNode[] XPath {XP*} 0 True False False All String Command: Microsoft.PowerShell.Utility/Select-Xml Set: Path Name Aliases Position Mandatory Pipeline ByName Provider Type ---- ------- -------- --------- -------- ------ -------- ---- Namespace {Na*} Named False False False All Hashtable Path {Pa*} 1 True False True All String[] XPath {XP*} 0 True False False All String ... This example returns a PSObject which displays the parameters sorted by the Parameter Sets for the cmdlet Select-XML. .EXAMPLE Get-Parameter -CommandName Select-Xml -SetName Path Command: Microsoft.PowerShell.Utility/Select-Xml Set: Path Name Aliases Position Mandatory Pipeline ByName Provider Type ---- ------- -------- --------- -------- ------ -------- ---- Namespace {Na*} Named False False False All Hashtable Path {Pa*} 1 True False True All String[] XPath {XP*} 0 True False False All String This example returns a PSObject which displays the parameters filtered by the Parameter Set 'Path' for the cmdlet Select-XML. .NOTES With many thanks to Joel Bennett, Jason Archer, Shay Levy, Hal Rottenberg, Oisin Grehan #> [CmdletBinding( DefaultParameterSetName = 'ParameterName' )] param( # The name of the command to get parameters for. [Parameter( Position = 1, Mandatory, ValueFromPipelineByPropertyName )] [Alias('Name')] [string[]]$CommandName, # The parameter name to filter by (allows Wilcards). [Parameter( Position = 2, ValueFromPipelineByPropertyName, ParameterSetName = 'FilterNames' )] [string[]]$ParameterName = '*', # The ParameterSet name to filter by (allows wildcards). [Parameter( ValueFromPipelineByPropertyName, ParameterSetName = 'FilterSets')] [string[]]$SetName = '*', # The name of the module which contains the command (this is for scoping). [Parameter( ValueFromPipelineByPropertyName )] [string]$ModuleName, # Skip testing for Provider parameters (will be much faster) [switch]$SkipProviderParameters, # Forces including the CommonParameters in the output [switch]$Force ) begin { function Join-ParameterObject { param( [PSObject]$ParameterMetadata, [object]$ParameterSetData ) # Sort ParameterMetadata and $ParameterSetData objects alphabetical and remove duplicate properties. [string[]]$SortedParameterMetadata = ($ParameterMetadata | Get-Member -MemberType Properties | Sort-Object -Property Name).Name foreach ($ParameterSetDataProperty in ($ParameterSetData | Get-Member -MemberType Properties | Where-Object { $SortedParameterMetadata -notcontains $_.Name }).Name) { $ParameterMetadata | Add-Member -MemberType NoteProperty -Name $ParameterSetDataProperty -Value $ParameterSetData.$ParameterSetDataProperty } [PSCustomObject]@{ PSTypeName = 'Omnicit.Get.Parameter' Name = $ParameterMetadata.Name Position = if ($ParameterMetadata.Position -lt 0) { 'Named' } else { $ParameterMetadata.Position } Aliases = $ParameterMetadata.Aliases Short = $ParameterMetadata.Name Type = $ParameterMetadata.ParameterType.Name ParameterSet = $ParamSet Command = $Command Mandatory = $ParameterMetadata.IsMandatory Provider = $ParameterMetadata.DynamicProvider ValueFromPipeline = $ParameterMetadata.ValueFromPipeline ValueFromPipelineByPropertyName = $ParameterMetadata.ValueFromPipelineByPropertyName } } function Add-Parameter { [CmdletBinding()] param ( # Parameter used to provide the Hashtable to the pipeline for the $Parameters variable. [Hashtable]$OutputParameter, # The actual parameters from the $Command [Management.Automation.ParameterMetadata[]]$InputParameter ) foreach ($Parameter in $InputParameter | Where-Object { !$OutputParameter.ContainsKey($_.Name) } ) { Write-Debug ('INITIALLY: ' + $Parameter.Name) $OutputParameter.($Parameter.Name) = $Parameter | Select-Object -Property '*' } [Array]$DynamicParameter = $InputParameter | Where-Object { $_.IsDynamic } if ($DynamicParameter) { foreach ($DynamicParam in $DynamicParameter) { if (Get-Member -InputObject $OutputParameter.($DynamicParam.Name) -Name DynamicProvider) { Write-Debug ('ADD:' + $DynamicParam.Name + ' ' + $Provider.Name) $OutputParameter.($DynamicParam.Name).DynamicProvider += $Provider.Name } else { Write-Debug ('CREATE:' + $DynamicParam.Name + ' ' + $Provider.Name) $OutputParameter.($DynamicParam.Name) = $OutputParameter.($DynamicParam.Name) | Select-Object -Property '*', @{ n = 'DynamicProvider'; e = { @($Provider.Name) } } } } } } } process { foreach ($Cmd in $CommandName) { if ($ModuleName) { $Cmd = "$ModuleName\$Cmd" } Write-Verbose "Searching for $Cmd" try { $Command = @(Get-Command -Name $Cmd -ErrorAction Stop) } catch { $PSCmdlet.ThrowTerminatingError($_) } # Resolve aliases (an alias can point to another alias) that's why a while is used and not an if set. while ($Command.CommandType -eq 'Alias') { try { $Command = @(Get-Command -Name ($Command.Definition) -ErrorAction Stop)[0] } catch { continue } } if ($null -eq $Command) { Write-Warning -Message ('No command with name "{0}" found' -f $Command.Name) continue } Write-Verbose -Message ('Get-Parameter(s) for {0}\ {1}' -f $Command.Source, $Command.Name) $Parameters = @{ } # Detect provider parameters, i.e. Get-ChildItem. $NoProviderParameters = -not $SkipProviderParameters # Assume only the core commands have dynamic provider parameters. if (-not $SkipProviderParameters -and $Command.Source -eq 'Microsoft.PowerShell.Management') { # Only validate commands that has a parameter which could accept a string path. foreach ($Param in $Command.Parameters.Values) { if (([String[]], [String] -contains $Param.ParameterType) -and ($Param.ParameterSets.Values | Where-Object { $_.Position -ge 0 })) { $NoProviderParameters = $false break } } } if ($NoProviderParameters) { if ($Command.Parameters) { Add-Parameter -OutputParameter $Parameters -InputParameter $Command.Parameters.Values } } else { foreach ($Provider in @(Get-PSProvider)) { if ($Provider.Drives.Length -gt 0) { $Drive = Get-Location -PSProvider $Provider.Name } else { $Drive = '{0}\ {1}::\' -f $Provider.ModuleName, $Provider.Name } Write-Verbose ("Get-Command $Command -Args $Drive | Select-Object -Expand Parameters") try { $MoreParameters = (Get-Command $Command -Args $Drive -ErrorAction Stop).Parameters.Values } catch { Write-Verbose -Message ('No provider parameters was found for "{0}" and PSProvider "{1}"' -f $Command, $Provider.Name) } if ($MoreParameters.Length -gt 0) { Add-Parameter -OutputParameter $Parameters -InputParameter $MoreParameters } } # If for some reason none of the drive paths worked, just use the default parameters if ($Parameters.Length -eq 0) { if ($Command.Parameters) { Add-Parameter -OutputParameter $Parameters -InputParameter $Command.Parameters.Values } } } # Calculate the shortest distinct parameter name (alias) - Do this BEFORE removing the common parameters or anything else. $Aliases = $Parameters.Values | Select-Object -ExpandProperty Aliases ## Get defined aliases $ParameterNames = $Parameters.Keys + $Aliases foreach ($ParameterNameKey in $($Parameters.Keys)) { $Aliases = @($ParameterNameKey) + @($Parameters.$ParameterNameKey.Aliases) | Sort-Object { $_.Length } $Shortest = '^{0}' -f @($Aliases)[0] foreach ($Alias in $Aliases) { $Short = '^' foreach ($Char in [char[]]$Alias) { $Short += $Char $MinimumCharCount = ($ParameterNames -match $Short).Count if ($MinimumCharCount -eq 1 ) { if ($Short.Length -lt $Shortest.Length) { $Shortest = $Short } break } } } if ($Shortest.Length -lt @($Aliases)[0].Length + 1) { # Overwrite the Aliases with this new value $Parameters.$ParameterNameKey = $Parameters.$ParameterNameKey | Add-Member NoteProperty Aliases ($Parameters.$ParameterNameKey.Aliases + @("$($Shortest.SubString(1))*")) -Force -Passthru } } $CommonParameters = [string[]][System.Management.Automation.Cmdlet]::CommonParameters foreach ($ParamSet in @($Command.ParameterSets.Name)) { $ParamSet = $ParamSet | Add-Member -Name IsDefault -MemberType NoteProperty -Value ($ParamSet -eq $Command.DefaultParameterSet) -PassThru foreach ($Parameter in $Parameters.Keys | Sort-Object) { # Write-Verbose "Parameter: $Parameter" if (-not $Force -and ($CommonParameters -contains $Parameter)) { continue } if ($Parameters.$Parameter.ParameterSets.ContainsKey($ParamSet) -or $Parameters.$Parameter.ParameterSets.ContainsKey('__AllParameterSets')) { if ($Parameters.$Parameter.ParameterSets.ContainsKey($ParamSet)) { $Output = Join-ParameterObject -ParameterMetadata $Parameters.$Parameter -ParameterSetData $Parameters.$Parameter.ParameterSets.$ParamSet } else { $Output = Join-ParameterObject -ParameterMetadata $Parameters.$Parameter -ParameterSetData $Parameters.$Parameter.ParameterSets.__AllParameterSets } $Output | Where-Object { $(foreach ($pn in $ParameterName) { $_.Name -like $Pn }) -contains $true } | Where-Object { $(foreach ($sn in $SetName) { $_.ParameterSet -like $sn }) -contains $true } } } } } } } function Get-WanIPAddress { <# .SYNOPSIS The Get-WanIPAddress function retrieves the current external IP address. .DESCRIPTION The Get-WanIPAddress function retrieves the current external IP address using ifconfig.co as the API provider. .EXAMPLE Get-WanIPAddress IP Address Country City Hostname ISP ---------- ------- ---- -------- --- 123.45.67.89 Sweden Gothenburg h-123-45.A56.priv.contoso.com Contso.com .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Get-WanIPAddress.md #> [Alias('Get-ExternalIP','gwan')] [CmdletBinding( SupportsShouldProcess )] param() try { if ($PSCmdlet.ShouldProcess($env:COMPUTERNAME)) { [Net.ServicePointManager]::SecurityProtocol = 3072, 768, 192 # TLS12, TLS11, TLS $Json = Invoke-RestMethod -Method Get -Uri 'https://ifconfig.co/json' -ErrorAction Stop [PSCustomObject]@{ PSTypeName = 'Omnicit.Get.WanIPAddress' IP_address = $Json.ip IP_decimal = $Json.ip_decimal Country = $Json.country EU_Country = $Json.country_eu ISO_Country = $Json.country_iso City = $Json.city Hostname = $Json.hostname Latitude = $Json.latitude Longitude = $Json.longitude Autonomous_System_Number = $Json.asn ISP = $Json.asn_org } } } catch { $PSCmdlet.ThrowTerminatingError($_) } } function Invoke-ModuleUpdate { <# .SYNOPSIS Update one, several or all installed modules if an update is available from a repository location. .DESCRIPTION Invoke-ModuleUpdate for installed modules that have a repository location, for example from PowerShell Gallery. The function, without the Update parameter, returns the current and the latest version available for each installed module with a repository location. If there is any existing installed versions for each module that current version number will be displayed under Multiple versions. When the Update parameter is issued the function will update the named modules. The script is based on the "Check-ModuleUpdate.ps1" from Jeffery Hicks* to check for available updates for installed PowerShell modules. *Credit: http://jdhitsolutions.com/blog/powershell/5441/check-for-module-updates/ .PARAMETER Name Specifies names or name patterns of modules that this function gets. Wildcard characters are permitted. .PARAMETER Update Switch parameter to invoke the 'Update-Module' cmdlet for the targeted modules. The default behavior without this switch is that the function will only list the current and available versions. .PARAMETER Force Switch parameter forces the update of each specified module, regardless of the current version of the module installed. Using the 'Force' parameter without using 'Update' parameter does not perform anything extra. .EXAMPLE Invoke-ModuleUpdate Name Current Version Online Version Multiple Versions ---- --------------- -------------- ----------------- SpeculationControl 1.0.0 1.0.8 False AzureAD 2.0.0.131 2.0.1.10 {2.0.0.115} AzureADPreview 2.0.0.154 2.0.1.11 {2.0.0.137} ISESteroids 2.7.1.7 2.7.1.7 {2.6.3.30} MicrosoftTeams 0.9.1 0.9.3 False NTFSSecurity 4.2.3 4.2.3 False Office365Connect 1.5.0 1.5.0 False ... ... ... ... This example returns the current and the latest version available for all installed modules that have a repository location. .EXAMPLE Invoke-ModuleUpdate -Update Name Current Version Online Version Multiple Versions ---- --------------- -------------- ----------------- SpeculationControl 1.0.8 1.0.8 {1.0.0} AzureAD 2.0.1.10 2.0.1.10 {2.0.0.131, 2.0.0.115} AzureADPreview 2.0.1.11 2.0.1.11 {2.0.0.137, 2.0.0.154} ISESteroids 2.7.1.7 2.7.1.7 {2.6.3.30} MicrosoftTeams 0.9.3 0.9.3 {0.9.1} NTFSSecurity 4.2.3 4.2.3 False Office365Connect 1.5.0 1.5.0 False ... ... ... ... This example installs the latest version available for all installed modules that have a repository location. .EXAMPLE Invoke-ModuleUpdate -Name 'AzureAD', 'PSScriptAnalyzer' -Update -Force Name Current Version Online Version Multiple Versions ---- --------------- -------------- ----------------- AzureAD 2.0.1.10 2.0.1.10 {2.0.0.131, 2.0.0.115} PSScriptAnalyzer 1.17.1 1.17.1 {1.17.0} This example will force install the latest version available for the AzureAD and PSScriptAnalyzer modules. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Invoke-ModuleUpdate.md #> [CmdletBinding( DefaultParameterSetName = 'NoUpdate', SupportsShouldProcess )] param ( # Specifies names or name patterns of modules that this cmdlet gets. Wildcard characters are permitted. [Parameter( ParameterSetName = 'NoUpdate', ValueFromPipeline, Position = 0 )] [Parameter( ParameterSetName = 'Update', ValueFromPipeline, Position = 0 )] [ValidateNotNullOrEmpty()] [SupportsWildcards()] [string[]]$Name = '*', # Switch parameter to invoke the 'Update-Module' cmdlet for the targeted modules. The default behavior without this switch is that the function will only list the current and available versions for installed modules. [Parameter( ParameterSetName = 'Update', Position = 1 )] [switch]$Update, # Switch parameter forces the update of each specified module, regardless of the current version of the module installed. Using the 'Force' parameter without using 'Update' parameter does not perform anything extra. [Parameter( ParameterSetName = 'Update', Position = 2 )] [switch]$Force ) begin { if ((-not $PSBoundParameters.ContainsKey('Update')) -and $PSBoundParameters.ContainsKey('Force')) { Write-Verbose -Message 'Using the "Force" parameter without using "Update" parameter does not perform anything extra.' } try { [array]$AllModules = (Get-Module -Name $Name -ListAvailable -ErrorAction Stop -Verbose:$false).Where{ $null -ne $_.RepositorySourceLocation } # Group all modules to exclude multiple versions. [array]$Modules = $AllModules | Group-Object -Property Name [int]$TotalCount = $Modules.Count switch ($Update) { $true { [string]$Status = 'Updating module' } Default { [string]$Status = 'Looking for the latest version of module' } } } catch { $PSCmdlet.ThrowTerminatingError($_) } try { # To speed up the 'Find-Module' cmdlet and not query all available session repositories. [array][PSCustomObject]$Repositories = Get-PSRepository -ErrorAction Stop if ($PSCmdLet.ParameterSetName -eq 'Update' -and $Repositories.InstallationPolicy -contains 'Untrusted') { Write-Verbose -Message 'One or more repositories have the InstallationPolicy set to Untrusted.' Write-Verbose -Message 'The function will temporary set all repositories to Trusted to avoid continues prompts of "Set-PSRepository" and revert back after finished updating.' $RepositoryChanged = $true foreach ($Repository in $Repositories) { Set-PSRepository -Name $Repository.Name -InstallationPolicy Trusted -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Verbose:$false } } else { $RepositoryChanged = $false } } catch { $PSCmdlet.ThrowTerminatingError($_) } } process { foreach ($Group in $Modules) { [int]$PercentComplete = ' {0:N0}' -f (($Modules.IndexOf($Group) / $TotalCount) * 100) Write-Progress -Activity (' {0} {1}' -f $Status, $Group.Group[0].Name) -Status (' {0}% Complete:' -f $PercentComplete) -PercentComplete $PercentComplete if ($PSCmdlet.ShouldProcess(('{0}' -f $Group.Group[0].Name), $MyInvocation.MyCommand.Name)) { $MultipleVersions = @() switch ($Group.Count) { ( { $PSItem -gt 1 }) { [string[]]$MultipleVersions = $Group.Group.Version[1..($Group.Group.Version.Length)] [PSModuleInfo]$Module = (($Group).Group | Sort-Object -Property Version -Descending)[0] } Default { $MultipleVersions = $null [PSModuleInfo]$Module = $Group.Group[0] } } try { if (($Repository = ($Repositories.Where{ [string]$_.SourceLocation -eq [string]$Module.RepositorySourceLocation }).Name)) { $FindModule = @{ Repository = $Repository ErrorAction = 'Stop' } } else { $FindModule = @{ ErrorAction = 'Stop' } } [PSCustomObject]$Online = Find-Module -Name $Module.Name @FindModule } catch { Write-Warning -Message ('Unable to find module {0}. Error: {1}' -f $Module.Name, $_.Exception.Message) continue } [version]$CurrentVersion = $Module.Version if ($PSBoundParameters.ContainsKey('Update')) { if ([version]$Online.Version -gt [version]$Module.Version) { try { Update-Module -Name $Module.Name -Force:$PSBoundParameters['Force'] -ErrorAction Stop [version]$CurrentVersion = $Online.Version $MultipleVersions += $Module.Version } catch { Write-Warning -Message ('Unable to update module. Error: {0}' -f $_.Exception.Message) [version]$CurrentVersion = $Module.Version } } else { [version]$CurrentVersion = $Online.Version } } # Output result to pipeline [PSCustomObject]@{ 'PSTypeName' = 'Omnicit.Invoke.ModuleUpdate' 'Name' = [string]$Module.Name 'Current Version' = $CurrentVersion 'Online Version' = $Online.Version 'Multiple Versions' = $MultipleVersions } } } } end { try { if ($RepositoryChanged) { Write-Verbose -Message 'Reverting back installation policies for repositories.' foreach ($Repository in $Repositories) { Set-PSRepository -Name $Repository.Name -InstallationPolicy $Repository.InstallationPolicy -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Verbose:$false } } } catch { $PSCmdlet.ThrowTerminatingError($_) } } } function Start-Mstsc { <# .SYNOPSIS Start a Mstsc process using predefined arguments from parameters. .DESCRIPTION The Start-Mstsc function starts a mstsc.exe process with arguments defined from the parameters. Built in check to validate that the current system is Windows. Shadow parameters are not yet implemented. Default port is defined as 3389. .EXAMPLE Start-Mstsc -ComputerName Server01 Starts mstsc.exe with the arguments /v:Server01:3389 Connects to Server01 using the default RDP port 3389. .EXAMPLE Start-Mstsc -ComputerName Server01 -Admin Starts mstsc.exe with the arguments /v:Server01:3389 /admin Connects to the console on Server01 using the default RDP port 3389. .EXAMPLE Start-Mstsc -ComputerName Server01 -Admin -Port 9876 Starts mstsc.exe with the arguments /v:Server01:9876 /admin Connects to the console on Server01 using port 9876. .EXAMPLE Start-Mstsc -ComputerName Server01 -Admin -Port 9876 -FullScreen Starts mstsc.exe with the arguments /v:Server01:9876 /admin /f Connects to the console on Server01 using port 9876 with full-screen. .EXAMPLE Start-Mstsc -ComputerName Server01 -Admin -Port 9876 -FullScreen -Prompt Starts mstsc.exe with the arguments /v:Server01:9876 /admin /f /prompt Connects to the console on Server01 using port 9876 with full-screen and prompt for credentials. .EXAMPLE Start-Mstsc -ComputerName Server01 -Admin -Port 9876 -FullScreen -Prompt -Public Starts mstsc.exe with the arguments /v:Server01:9876 /admin /f /prompt /public Connects to the console on Server01 using port 9876 with full-screen, prompt for credentials and run RDP in public mode. .EXAMPLE Start-Mstsc -ComputerName Server01 -RestrictedAdmin Starts mstsc.exe with the arguments /v:Server01:3389 /restrictedAdmin Connects to Server01 using the default RDP port 3389 with Restricted Admin. .EXAMPLE Start-Mstsc -ComputerName Server01 -RemoteGuard Starts mstsc.exe with the arguments /v:Server01:3389 /remoteGuard Connects to Server01 using the default RDP port 3389 with Remote Guard. .LINK https://github.com/Omnicit/Omnicit/blob/master/docs/en-US/Start-Mstsc.md #> [Alias('remote')] [CmdletBinding( SupportsShouldProcess, PositionalBinding, DefaultParameterSetName = 'Default' )] param( # Specifies the remote PC or Server to which you want to connect. [Parameter( Position = 1, HelpMessage = 'Specify the remote PC or Server to which you want to connect', Mandatory )] [Alias('Server', 'IPAddress', 'Target', 'Node', 'Client')] [string[]]$ComputerName, # Connects you to the console session for administering a remote PC. [Parameter( Position = 2 )] [Alias('Console')] [switch]$Admin, # Specifies the port of the remote PC to which you want to connect. Default value is 3389. [Parameter( Position = 3 )] [ValidateRange(1, 65535)] [ValidateNotNullOrEmpty()] [int]$Port = 3389, # Starts Remote Desktop in full-screen mode. [Parameter( ParameterSetName = 'FullScreen' )] [switch]$FullScreen, # Specifies the width of the Remote Desktop window. [Parameter( ParameterSetName = 'FixedSize', Mandatory )] [Alias('w')] [ValidateRange(1, 10000)] [int]$Width, # Specifies the height of the Remote Desktop window. [Parameter( ParameterSetName = 'FixedSize', Mandatory )] [Alias('h')] [ValidateRange(1, 10000)] [int]$Height, <# Matches the remote desktop width and height with the local virtual desktop, spanning across multiple monitors, if necessary. To span across monitors, the monitors must be arranged to form a rectangle. #> [Parameter( ParameterSetName = 'Span' )] [switch]$Span, # Configures the Remote Desktop Services session monitor layout to be identical to the current client-side configuration. [Parameter( ParameterSetName = 'MultiMonitor' )] [Alias('multimon')] [switch]$MultiMonitor, # Prompts you for your credentials when you connect to the remote PC. [switch]$Prompt, # Runs Remote Desktop in public mode. [switch]$Public, <# Connects you to the remote PC in Restricted Administration mode. In this mode, credentials won't be sent to the remote PC, which can protect you if you connect to a PC that has been compromised. However, connections made from the remote PC might not be authenticated by other PCs, which might impact application functionality and compatibility. This parameter implies the admin parameter. #> [Parameter( ParameterSetName = 'RestrictedAdmin' )] [switch]$RestrictedAdmin, <# Connects your device to a remote device using Remote Guard. Remote Guard prevents credentials from being sent to the remote PC, which can help protect your credentials if you connect to a remote PC that has been compromised. Unlike Restricted Administration mode, Remote Guard also supports connections made from the remote PC by redirecting all requests back to your device. #> [Parameter( ParameterSetName = 'RemoteGuard' )] [switch]$RemoteGuard ) begin { try { $PreviousErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = 'Continue' if (Get-Variable IsWindows -ErrorAction SilentlyContinue) { if (-not $IsWindows) { throw [System.NotSupportedException]::New('Start-Mstsc is only supported on Windows operating systems.') } } else { Write-Verbose -Message 'Running Start-Mstsc as Windows PowerShell.' } } catch { $PSCmdlet.ThrowTerminatingError($_) } finally { $ErrorActionPreference = $PreviousErrorActionPreference } $RDTable = @{ 'Admin' = '/admin' 'Width' = ('/w:{0}' -f $Width) 'Height' = ('/h:{0}' -f $Height) 'FullScreen' = '/f' 'Span' = '/span' 'MultiMonitor' = '/multimon' 'Prompt' = '/prompt' 'Public' = '/public' 'RestrictedAdmin' = '/restrictedAdmin' 'RemoteGuard' = '/remoteGuard' } } process { foreach ($Computer in $ComputerName) { try { $RDTable.Add('ComputerName', ('/v:{0}:{1}' -f $Computer, $Port)) [Text.StringBuilder]$RDString = [Text.StringBuilder]::new() foreach ($Key in $PSBoundParameters.Keys) { if ($RDTable[$Key]) { $null = $RDString.Append($RDTable[$Key]) $null = $RDString.Append(' ') } } $RDTable.Remove('ComputerName') $RDArgument = $RDString.ToString().Trim() } catch { $PSCmdlet.ThrowTerminatingError($_) } if ($PSCmdlet.ShouldProcess($RDArgument)) { try { Write-Verbose -Message ('Starting mstsc with the following arguments: {0}' -f ($RDArgument)) Start-Process -FilePath "$env:windir\system32\mstsc.exe" -ArgumentList ($RDArgument) } catch { $PSCmdlet.ThrowTerminatingError($_) } } } } } |