PSSharedGoods.psm1
function Write-Color { <# .SYNOPSIS Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. .DESCRIPTION Write-Color is a wrapper around Write-Host delivering a lot of additional features for easier color options. It provides: - Easy manipulation of colors, - Logging output to file (log) - Nice formatting options out of the box. - Ability to use aliases for parameters .PARAMETER Text Text to display on screen and write to log file if specified. Accepts an array of strings. .PARAMETER Color Color of the text. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER BackGroundColor Color of the background. Accepts an array of colors. If more than one color is specified it will loop through colors for each string. If there are more strings than colors it will start from the beginning. Available colors are: Black, DarkBlue, DarkGreen, DarkCyan, DarkRed, DarkMagenta, DarkYellow, Gray, DarkGray, Blue, Green, Cyan, Red, Magenta, Yellow, White .PARAMETER StartTab Number of tabs to add before text. Default is 0. .PARAMETER LinesBefore Number of empty lines before text. Default is 0. .PARAMETER LinesAfter Number of empty lines after text. Default is 0. .PARAMETER StartSpaces Number of spaces to add before text. Default is 0. .PARAMETER LogFile Path to log file. If not specified no log file will be created. .PARAMETER DateTimeFormat Custom date and time format string. Default is yyyy-MM-dd HH:mm:ss .PARAMETER LogTime If set to $true it will add time to log file. Default is $true. .PARAMETER LogRetry Number of retries to write to log file, in case it can't write to it for some reason, before skipping. Default is 2. .PARAMETER Encoding Encoding of the log file. Default is Unicode. .PARAMETER ShowTime Switch to add time to console output. Default is not set. .PARAMETER NoNewLine Switch to not add new line at the end of the output. Default is not set. .PARAMETER NoConsoleOutput Switch to not output to console. Default all output goes to console. .EXAMPLE Write-Color -Text "Red ", "Green ", "Yellow " -Color Red,Green,Yellow .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan .EXAMPLE Write-Color -Text "This is text in Green ", "followed by red ", "and then we have Magenta... ", "isn't it fun? ", "Here goes DarkCyan" -Color Green,Red,Magenta,White,DarkCyan -StartTab 3 -LinesBefore 1 -LinesAfter 1 .EXAMPLE Write-Color "1. ", "Option 1" -Color Yellow, Green Write-Color "2. ", "Option 2" -Color Yellow, Green Write-Color "3. ", "Option 3" -Color Yellow, Green Write-Color "4. ", "Option 4" -Color Yellow, Green Write-Color "9. ", "Press 9 to exit" -Color Yellow, Gray -LinesBefore 1 .EXAMPLE Write-Color -LinesBefore 2 -Text "This little ","message is ", "written to log ", "file as well." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" -TimeFormat "yyyy-MM-dd HH:mm:ss" Write-Color -Text "This can get ","handy if ", "want to display things, and log actions to file ", "at the same time." ` -Color Yellow, White, Green, Red, Red -LogFile "C:\testing.txt" .EXAMPLE Write-Color -T "My text", " is ", "all colorful" -C Yellow, Red, Green -B Green, Green, Yellow Write-Color -t "my text" -c yellow -b green Write-Color -text "my text" -c red .EXAMPLE Write-Color -Text "Testuję czy się ładnie zapisze, czy będą problemy" -Encoding unicode -LogFile 'C:\temp\testinggg.txt' -Color Red -NoConsoleOutput .NOTES Understanding Custom date and time format strings: https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-date-and-time-format-strings Project support: https://github.com/EvotecIT/PSWriteColor Original idea: Josh (https://stackoverflow.com/users/81769/josh) #> [alias('Write-Colour')] [CmdletBinding()] param ( [alias ('T')] [String[]]$Text, [alias ('C', 'ForegroundColor', 'FGC')] [ConsoleColor[]]$Color = [ConsoleColor]::White, [alias ('B', 'BGC')] [ConsoleColor[]]$BackGroundColor = $null, [alias ('Indent')][int] $StartTab = 0, [int] $LinesBefore = 0, [int] $LinesAfter = 0, [int] $StartSpaces = 0, [alias ('L')] [string] $LogFile = '', [Alias('DateFormat', 'TimeFormat')][string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss', [alias ('LogTimeStamp')][bool] $LogTime = $true, [int] $LogRetry = 2, [ValidateSet('unknown', 'string', 'unicode', 'bigendianunicode', 'utf8', 'utf7', 'utf32', 'ascii', 'default', 'oem')][string]$Encoding = 'Unicode', [switch] $ShowTime, [switch] $NoNewLine, [alias('HideConsole')][switch] $NoConsoleOutput ) if (-not $NoConsoleOutput) { $DefaultColor = $Color[0] if ($null -ne $BackGroundColor -and $BackGroundColor.Count -ne $Color.Count) { Write-Error "Colors, BackGroundColors parameters count doesn't match. Terminated." return } if ($LinesBefore -ne 0) { for ($i = 0; $i -lt $LinesBefore; $i++) { Write-Host -Object "`n" -NoNewline } } if ($StartTab -ne 0) { for ($i = 0; $i -lt $StartTab; $i++) { Write-Host -Object "`t" -NoNewline } } if ($StartSpaces -ne 0) { for ($i = 0; $i -lt $StartSpaces; $i++) { Write-Host -Object ' ' -NoNewline } } if ($ShowTime) { Write-Host -Object "[$([datetime]::Now.ToString($DateTimeFormat))] " -NoNewline } if ($Text.Count -ne 0) { if ($Color.Count -ge $Text.Count) { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } } else { for ($i = 0; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } } } else { if ($null -eq $BackGroundColor) { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -NoNewline } } else { for ($i = 0; $i -lt $Color.Length ; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $Color[$i] -BackgroundColor $BackGroundColor[$i] -NoNewline } for ($i = $Color.Length; $i -lt $Text.Length; $i++) { Write-Host -Object $Text[$i] -ForegroundColor $DefaultColor -BackgroundColor $BackGroundColor[0] -NoNewline } } } } if ($NoNewLine -eq $true) { Write-Host -NoNewline } else { Write-Host } if ($LinesAfter -ne 0) { for ($i = 0; $i -lt $LinesAfter; $i++) { Write-Host -Object "`n" -NoNewline } } } if ($Text.Count -and $LogFile) { $TextToFile = "" for ($i = 0; $i -lt $Text.Length; $i++) { $TextToFile += $Text[$i] } $Saved = $false $Retry = 0 Do { $Retry++ try { if ($LogTime) { "[$([datetime]::Now.ToString($DateTimeFormat))] $TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } else { "$TextToFile" | Out-File -FilePath $LogFile -Encoding $Encoding -Append -ErrorAction Stop -WhatIf:$false } $Saved = $true } catch { if ($Saved -eq $false -and $Retry -eq $LogRetry) { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Tried ($Retry/$LogRetry))" } else { Write-Warning "Write-Color - Couldn't write to log file $($_.Exception.Message). Retrying... ($Retry/$LogRetry)" } } } Until ($Saved -eq $true -or $Retry -ge $LogRetry) } } function Get-PSRegistryDictionaries { <# .SYNOPSIS Retrieves a set of registry dictionaries for common registry hives and keys. .DESCRIPTION This function retrieves a set of registry dictionaries that provide mappings for common registry hives and keys. These dictionaries can be used to easily reference different registry locations in PowerShell scripts. .EXAMPLE Get-PSRegistryDictionaries Description: Retrieves all the registry dictionaries. #> [cmdletBinding()] param() if ($Script:Dictionary) { return } $Script:Dictionary = @{ 'HKUAD:' = 'HKEY_ALL_USERS_DEFAULT' 'HKUA:' = 'HKEY_ALL_USERS' 'HKUD:' = 'HKEY_DEFAULT_USER' 'HKUDUD:' = 'HKEY_ALL_DOMAIN_USERS_DEFAULT' 'HKUDU:' = 'HKEY_ALL_DOMAIN_USERS' 'HKUDUO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER' 'HKUDUDO:' = 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' 'HKCR:' = 'HKEY_CLASSES_ROOT' 'HKCU:' = 'HKEY_CURRENT_USER' 'HKLM:' = 'HKEY_LOCAL_MACHINE' 'HKU:' = 'HKEY_USERS' 'HKCC:' = 'HKEY_CURRENT_CONFIG' 'HKDD:' = 'HKEY_DYN_DATA' 'HKPD:' = 'HKEY_PERFORMANCE_DATA' } $Script:HiveDictionary = [ordered] @{ 'HKEY_ALL_USERS_DEFAULT' = 'All+Default' 'HKUAD' = 'All+Default' 'HKEY_ALL_USERS' = 'All' 'HKUA' = 'All' 'HKEY_ALL_DOMAIN_USERS_DEFAULT' = 'AllDomain+Default' 'HKUDUD' = 'AllDomain+Default' 'HKEY_ALL_DOMAIN_USERS' = 'AllDomain' 'HKUDU' = 'AllDomain' 'HKEY_DEFAULT_USER' = 'Default' 'HKUD' = 'Default' 'HKEY_ALL_DOMAIN_USERS_OTHER' = 'AllDomain+Other' 'HKUDUO' = 'AllDomain+Other' 'HKUDUDO' = 'AllDomain+Other+Default' 'HKEY_ALL_DOMAIN_USERS_OTHER_DEFAULT' = 'AllDomain+Other+Default' 'HKEY_CLASSES_ROOT' = 'ClassesRoot' 'HKCR' = 'ClassesRoot' 'ClassesRoot' = 'ClassesRoot' 'HKCU' = 'CurrentUser' 'HKEY_CURRENT_USER' = 'CurrentUser' 'CurrentUser' = 'CurrentUser' 'HKLM' = 'LocalMachine' 'HKEY_LOCAL_MACHINE' = 'LocalMachine' 'LocalMachine' = 'LocalMachine' 'HKU' = 'Users' 'HKEY_USERS' = 'Users' 'Users' = 'Users' 'HKCC' = 'CurrentConfig' 'HKEY_CURRENT_CONFIG' = 'CurrentConfig' 'CurrentConfig' = 'CurrentConfig' 'HKDD' = 'DynData' 'HKEY_DYN_DATA' = 'DynData' 'DynData' = 'DynData' 'HKPD' = 'PerformanceData' 'HKEY_PERFORMANCE_DATA ' = 'PerformanceData' 'PerformanceData' = 'PerformanceData' } $Script:ReverseTypesDictionary = [ordered] @{ 'REG_SZ' = 'string' 'REG_NONE' = 'none' 'REG_EXPAND_SZ' = 'expandstring' 'REG_BINARY' = 'binary' 'REG_DWORD' = 'dword' 'REG_MULTI_SZ' = 'multistring' 'REG_QWORD' = 'qword' 'string' = 'string' 'expandstring' = 'expandstring' 'binary' = 'binary' 'dword' = 'dword' 'multistring' = 'multistring' 'qword' = 'qword' 'none' = 'none' } } $Script:RGBColors = [ordered] @{ None = $null AirForceBlue = 93, 138, 168 Akaroa = 195, 176, 145 AlbescentWhite = 227, 218, 201 AliceBlue = 240, 248, 255 Alizarin = 227, 38, 54 Allports = 18, 97, 128 Almond = 239, 222, 205 AlmondFrost = 159, 129, 112 Amaranth = 229, 43, 80 Amazon = 59, 122, 87 Amber = 255, 191, 0 Amethyst = 153, 102, 204 AmethystSmoke = 156, 138, 164 AntiqueWhite = 250, 235, 215 Apple = 102, 180, 71 AppleBlossom = 176, 92, 82 Apricot = 251, 206, 177 Aqua = 0, 255, 255 Aquamarine = 127, 255, 212 Armygreen = 75, 83, 32 Arsenic = 59, 68, 75 Astral = 54, 117, 136 Atlantis = 164, 198, 57 Atomic = 65, 74, 76 AtomicTangerine = 255, 153, 102 Axolotl = 99, 119, 91 Azure = 240, 255, 255 Bahia = 176, 191, 26 BakersChocolate = 93, 58, 26 BaliHai = 124, 152, 171 BananaMania = 250, 231, 181 BattleshipGrey = 85, 93, 80 BayOfMany = 35, 48, 103 Beige = 245, 245, 220 Bermuda = 136, 216, 192 Bilbao = 42, 128, 0 BilobaFlower = 181, 126, 220 Bismark = 83, 104, 114 Bisque = 255, 228, 196 Bistre = 61, 43, 31 Bittersweet = 254, 111, 94 Black = 0, 0, 0 BlackPearl = 31, 38, 42 BlackRose = 85, 31, 47 BlackRussian = 23, 24, 43 BlanchedAlmond = 255, 235, 205 BlizzardBlue = 172, 229, 238 Blue = 0, 0, 255 BlueDiamond = 77, 26, 127 BlueMarguerite = 115, 102, 189 BlueSmoke = 115, 130, 118 BlueViolet = 138, 43, 226 Blush = 169, 92, 104 BokaraGrey = 22, 17, 13 Bole = 121, 68, 59 BondiBlue = 0, 147, 175 Bordeaux = 88, 17, 26 Bossanova = 86, 60, 92 Boulder = 114, 116, 114 Bouquet = 183, 132, 167 Bourbon = 170, 108, 57 Brass = 181, 166, 66 BrickRed = 199, 44, 72 BrightGreen = 102, 255, 0 BrightRed = 146, 43, 62 BrightTurquoise = 8, 232, 222 BrilliantRose = 243, 100, 162 BrinkPink = 250, 110, 121 BritishRacingGreen = 0, 66, 37 Bronze = 205, 127, 50 Brown = 165, 42, 42 BrownPod = 57, 24, 2 BuddhaGold = 202, 169, 6 Buff = 240, 220, 130 Burgundy = 128, 0, 32 BurlyWood = 222, 184, 135 BurntOrange = 255, 117, 56 BurntSienna = 233, 116, 81 BurntUmber = 138, 51, 36 ButteredRum = 156, 124, 56 CadetBlue = 95, 158, 160 California = 224, 141, 60 CamouflageGreen = 120, 134, 107 Canary = 255, 255, 153 CanCan = 217, 134, 149 CannonPink = 145, 78, 117 CaputMortuum = 89, 39, 32 Caramel = 255, 213, 154 Cararra = 237, 230, 214 Cardinal = 179, 33, 52 CardinGreen = 18, 53, 36 CareysPink = 217, 152, 160 CaribbeanGreen = 0, 222, 164 Carmine = 175, 0, 42 CarnationPink = 255, 166, 201 CarrotOrange = 242, 142, 28 Cascade = 141, 163, 153 CatskillWhite = 226, 229, 222 Cedar = 67, 48, 46 Celadon = 172, 225, 175 Celeste = 207, 207, 196 Cello = 55, 79, 107 Cement = 138, 121, 93 Cerise = 222, 49, 99 Cerulean = 0, 123, 167 CeruleanBlue = 42, 82, 190 Chantilly = 239, 187, 204 Chardonnay = 255, 200, 124 Charlotte = 167, 216, 222 Charm = 208, 116, 139 Chartreuse = 127, 255, 0 ChartreuseYellow = 223, 255, 0 ChelseaCucumber = 135, 169, 107 Cherub = 246, 214, 222 Chestnut = 185, 78, 72 ChileanFire = 226, 88, 34 Chinook = 150, 200, 162 Chocolate = 210, 105, 30 Christi = 125, 183, 0 Christine = 181, 101, 30 Cinnabar = 235, 76, 66 Citron = 159, 169, 31 Citrus = 141, 182, 0 Claret = 95, 25, 51 ClassicRose = 251, 204, 231 ClayCreek = 145, 129, 81 Clinker = 75, 54, 33 Clover = 74, 93, 35 Cobalt = 0, 71, 171 CocoaBrown = 44, 22, 8 Cola = 60, 48, 36 ColumbiaBlue = 166, 231, 255 CongoBrown = 103, 76, 71 Conifer = 178, 236, 93 Copper = 218, 138, 103 CopperRose = 153, 102, 102 Coral = 255, 127, 80 CoralRed = 255, 64, 64 CoralTree = 173, 111, 105 Coriander = 188, 184, 138 Corn = 251, 236, 93 CornField = 250, 240, 190 Cornflower = 147, 204, 234 CornflowerBlue = 100, 149, 237 Cornsilk = 255, 248, 220 Cosmic = 132, 63, 91 Cosmos = 255, 204, 203 CostaDelSol = 102, 93, 30 CottonCandy = 255, 188, 217 Crail = 164, 90, 82 Cranberry = 205, 96, 126 Cream = 255, 255, 204 CreamCan = 242, 198, 73 Crimson = 220, 20, 60 Crusta = 232, 142, 90 Cumulus = 255, 255, 191 Cupid = 246, 173, 198 CuriousBlue = 40, 135, 200 Cyan = 0, 255, 255 Cyprus = 6, 78, 64 DaisyBush = 85, 53, 146 Dandelion = 250, 218, 94 Danube = 96, 130, 182 DarkBlue = 0, 0, 139 DarkBrown = 101, 67, 33 DarkCerulean = 8, 69, 126 DarkChestnut = 152, 105, 96 DarkCoral = 201, 90, 73 DarkCyan = 0, 139, 139 DarkGoldenrod = 184, 134, 11 DarkGray = 169, 169, 169 DarkGreen = 0, 100, 0 DarkGreenCopper = 73, 121, 107 DarkGrey = 169, 169, 169 DarkKhaki = 189, 183, 107 DarkMagenta = 139, 0, 139 DarkOliveGreen = 85, 107, 47 DarkOrange = 255, 140, 0 DarkOrchid = 153, 50, 204 DarkPastelGreen = 3, 192, 60 DarkPink = 222, 93, 131 DarkPurple = 150, 61, 127 DarkRed = 139, 0, 0 DarkSalmon = 233, 150, 122 DarkSeaGreen = 143, 188, 143 DarkSlateBlue = 72, 61, 139 DarkSlateGray = 47, 79, 79 DarkSlateGrey = 47, 79, 79 DarkSpringGreen = 23, 114, 69 DarkTangerine = 255, 170, 29 DarkTurquoise = 0, 206, 209 DarkViolet = 148, 0, 211 DarkWood = 130, 102, 68 DeepBlush = 245, 105, 145 DeepCerise = 224, 33, 138 DeepKoamaru = 51, 51, 102 DeepLilac = 153, 85, 187 DeepMagenta = 204, 0, 204 DeepPink = 255, 20, 147 DeepSea = 14, 124, 97 DeepSkyBlue = 0, 191, 255 DeepTeal = 24, 69, 59 Denim = 36, 107, 206 DesertSand = 237, 201, 175 DimGray = 105, 105, 105 DimGrey = 105, 105, 105 DodgerBlue = 30, 144, 255 Dolly = 242, 242, 122 Downy = 95, 201, 191 DutchWhite = 239, 223, 187 EastBay = 76, 81, 109 EastSide = 178, 132, 190 EchoBlue = 169, 178, 195 Ecru = 194, 178, 128 Eggplant = 162, 0, 109 EgyptianBlue = 16, 52, 166 ElectricBlue = 125, 249, 255 ElectricIndigo = 111, 0, 255 ElectricLime = 208, 255, 20 ElectricPurple = 191, 0, 255 Elm = 47, 132, 124 Emerald = 80, 200, 120 Eminence = 108, 48, 130 Endeavour = 46, 88, 148 EnergyYellow = 245, 224, 80 Espresso = 74, 44, 42 Eucalyptus = 26, 162, 96 Falcon = 126, 94, 96 Fallow = 204, 153, 102 FaluRed = 128, 24, 24 Feldgrau = 77, 93, 83 Feldspar = 205, 149, 117 Fern = 113, 188, 120 FernGreen = 79, 121, 66 Festival = 236, 213, 64 Finn = 97, 64, 81 FireBrick = 178, 34, 34 FireBush = 222, 143, 78 FireEngineRed = 211, 33, 45 Flamingo = 233, 92, 75 Flax = 238, 220, 130 FloralWhite = 255, 250, 240 ForestGreen = 34, 139, 34 Frangipani = 250, 214, 165 FreeSpeechAquamarine = 0, 168, 119 FreeSpeechRed = 204, 0, 0 FrenchLilac = 230, 168, 215 FrenchRose = 232, 83, 149 FriarGrey = 135, 134, 129 Froly = 228, 113, 122 Fuchsia = 255, 0, 255 FuchsiaPink = 255, 119, 255 Gainsboro = 220, 220, 220 Gallery = 219, 215, 210 Galliano = 204, 160, 29 Gamboge = 204, 153, 0 Ghost = 196, 195, 208 GhostWhite = 248, 248, 255 Gin = 216, 228, 188 GinFizz = 247, 231, 206 Givry = 230, 208, 171 Glacier = 115, 169, 194 Gold = 255, 215, 0 GoldDrop = 213, 108, 43 GoldenBrown = 150, 113, 23 GoldenFizz = 240, 225, 48 GoldenGlow = 248, 222, 126 GoldenPoppy = 252, 194, 0 Goldenrod = 218, 165, 32 GoldenSand = 233, 214, 107 GoldenYellow = 253, 238, 0 GoldTips = 225, 189, 39 GordonsGreen = 37, 53, 41 Gorse = 255, 225, 53 Gossamer = 49, 145, 119 GrannySmithApple = 168, 228, 160 Gray = 128, 128, 128 GrayAsparagus = 70, 89, 69 Green = 0, 128, 0 GreenLeaf = 76, 114, 29 GreenVogue = 38, 67, 72 GreenYellow = 173, 255, 47 Grey = 128, 128, 128 GreyAsparagus = 70, 89, 69 GuardsmanRed = 157, 41, 51 GumLeaf = 178, 190, 181 Gunmetal = 42, 52, 57 Hacienda = 155, 135, 12 HalfAndHalf = 232, 228, 201 HalfBaked = 95, 138, 139 HalfColonialWhite = 246, 234, 190 HalfPearlLusta = 240, 234, 214 HanPurple = 63, 0, 255 Harlequin = 74, 255, 0 HarleyDavidsonOrange = 194, 59, 34 Heather = 174, 198, 207 Heliotrope = 223, 115, 255 Hemp = 161, 122, 116 Highball = 134, 126, 54 HippiePink = 171, 75, 82 Hoki = 110, 127, 128 HollywoodCerise = 244, 0, 161 Honeydew = 240, 255, 240 Hopbush = 207, 113, 175 HorsesNeck = 108, 84, 30 HotPink = 255, 105, 180 HummingBird = 201, 255, 229 HunterGreen = 53, 94, 59 Illusion = 244, 152, 173 InchWorm = 202, 224, 13 IndianRed = 205, 92, 92 Indigo = 75, 0, 130 InternationalKleinBlue = 0, 24, 168 InternationalOrange = 255, 79, 0 IrisBlue = 28, 169, 201 IrishCoffee = 102, 66, 40 IronsideGrey = 113, 112, 110 IslamicGreen = 0, 144, 0 Ivory = 255, 255, 240 Jacarta = 61, 50, 93 JackoBean = 65, 54, 40 JacksonsPurple = 46, 45, 136 Jade = 0, 171, 102 JapaneseLaurel = 47, 117, 50 Jazz = 93, 43, 44 JazzberryJam = 165, 11, 94 JellyBean = 68, 121, 142 JetStream = 187, 208, 201 Jewel = 0, 107, 60 Jon = 79, 58, 60 JordyBlue = 124, 185, 232 Jumbo = 132, 132, 130 JungleGreen = 41, 171, 135 KaitokeGreen = 30, 77, 43 Karry = 255, 221, 202 KellyGreen = 70, 203, 24 Keppel = 93, 164, 147 Khaki = 240, 230, 140 Killarney = 77, 140, 87 KingfisherDaisy = 85, 27, 140 Kobi = 230, 143, 172 LaPalma = 60, 141, 13 LaserLemon = 252, 247, 94 Laurel = 103, 146, 103 Lavender = 230, 230, 250 LavenderBlue = 204, 204, 255 LavenderBlush = 255, 240, 245 LavenderPink = 251, 174, 210 LavenderRose = 251, 160, 227 LawnGreen = 124, 252, 0 LemonChiffon = 255, 250, 205 LightBlue = 173, 216, 230 LightCoral = 240, 128, 128 LightCyan = 224, 255, 255 LightGoldenrodYellow = 250, 250, 210 LightGray = 211, 211, 211 LightGreen = 144, 238, 144 LightGrey = 211, 211, 211 LightPink = 255, 182, 193 LightSalmon = 255, 160, 122 LightSeaGreen = 32, 178, 170 LightSkyBlue = 135, 206, 250 LightSlateGray = 119, 136, 153 LightSlateGrey = 119, 136, 153 LightSteelBlue = 176, 196, 222 LightYellow = 255, 255, 224 Lilac = 204, 153, 204 Lime = 0, 255, 0 LimeGreen = 50, 205, 50 Limerick = 139, 190, 27 Linen = 250, 240, 230 Lipstick = 159, 43, 104 Liver = 83, 75, 79 Lochinvar = 86, 136, 125 Lochmara = 38, 97, 156 Lola = 179, 158, 181 LondonHue = 170, 152, 169 Lotus = 124, 72, 72 LuckyPoint = 29, 41, 81 MacaroniAndCheese = 255, 189, 136 Madang = 193, 249, 162 Madras = 81, 65, 0 Magenta = 255, 0, 255 MagicMint = 170, 240, 209 Magnolia = 248, 244, 255 Mahogany = 215, 59, 62 Maire = 27, 24, 17 Maize = 230, 190, 138 Malachite = 11, 218, 81 Malibu = 93, 173, 236 Malta = 169, 154, 134 Manatee = 140, 146, 172 Mandalay = 176, 121, 57 MandarianOrange = 146, 39, 36 Mandy = 191, 79, 81 Manhattan = 229, 170, 112 Mantis = 125, 194, 66 Manz = 217, 230, 80 MardiGras = 48, 25, 52 Mariner = 57, 86, 156 Maroon = 128, 0, 0 Matterhorn = 85, 85, 85 Mauve = 244, 187, 255 Mauvelous = 255, 145, 175 MauveTaupe = 143, 89, 115 MayaBlue = 119, 181, 254 McKenzie = 129, 97, 60 MediumAquamarine = 102, 205, 170 MediumBlue = 0, 0, 205 MediumCarmine = 175, 64, 53 MediumOrchid = 186, 85, 211 MediumPurple = 147, 112, 219 MediumRedViolet = 189, 51, 164 MediumSeaGreen = 60, 179, 113 MediumSlateBlue = 123, 104, 238 MediumSpringGreen = 0, 250, 154 MediumTurquoise = 72, 209, 204 MediumVioletRed = 199, 21, 133 MediumWood = 166, 123, 91 Melon = 253, 188, 180 Merlot = 112, 54, 66 MetallicGold = 211, 175, 55 Meteor = 184, 115, 51 MidnightBlue = 25, 25, 112 MidnightExpress = 0, 20, 64 Mikado = 60, 52, 31 MilanoRed = 168, 55, 49 Ming = 54, 116, 125 MintCream = 245, 255, 250 MintGreen = 152, 255, 152 Mischka = 168, 169, 173 MistyRose = 255, 228, 225 Moccasin = 255, 228, 181 Mojo = 149, 69, 53 MonaLisa = 255, 153, 153 Mongoose = 179, 139, 109 Montana = 53, 56, 57 MoodyBlue = 116, 108, 192 MoonYellow = 245, 199, 26 MossGreen = 173, 223, 173 MountainMeadow = 28, 172, 120 MountainMist = 161, 157, 148 MountbattenPink = 153, 122, 141 Mulberry = 211, 65, 157 Mustard = 255, 219, 88 Myrtle = 25, 89, 5 MySin = 255, 179, 71 NavajoWhite = 255, 222, 173 Navy = 0, 0, 128 NavyBlue = 2, 71, 254 NeonCarrot = 255, 153, 51 NeonPink = 255, 92, 205 Nepal = 145, 163, 176 Nero = 20, 20, 20 NewMidnightBlue = 0, 0, 156 Niagara = 58, 176, 158 NightRider = 59, 47, 47 Nobel = 152, 152, 152 Norway = 169, 186, 157 Nugget = 183, 135, 39 OceanGreen = 95, 167, 120 Ochre = 202, 115, 9 OldCopper = 111, 78, 55 OldGold = 207, 181, 59 OldLace = 253, 245, 230 OldLavender = 121, 104, 120 OldRose = 195, 33, 72 Olive = 128, 128, 0 OliveDrab = 107, 142, 35 OliveGreen = 181, 179, 92 Olivetone = 110, 110, 48 Olivine = 154, 185, 115 Onahau = 196, 216, 226 Opal = 168, 195, 188 Orange = 255, 165, 0 OrangePeel = 251, 153, 2 OrangeRed = 255, 69, 0 Orchid = 218, 112, 214 OuterSpace = 45, 56, 58 OutrageousOrange = 254, 90, 29 Oxley = 95, 167, 119 PacificBlue = 0, 136, 220 Padua = 128, 193, 151 PalatinatePurple = 112, 41, 99 PaleBrown = 160, 120, 90 PaleChestnut = 221, 173, 175 PaleCornflowerBlue = 188, 212, 230 PaleGoldenrod = 238, 232, 170 PaleGreen = 152, 251, 152 PaleMagenta = 249, 132, 239 PalePink = 250, 218, 221 PaleSlate = 201, 192, 187 PaleTaupe = 188, 152, 126 PaleTurquoise = 175, 238, 238 PaleVioletRed = 219, 112, 147 PalmLeaf = 53, 66, 48 Panache = 233, 255, 219 PapayaWhip = 255, 239, 213 ParisDaisy = 255, 244, 79 Parsley = 48, 96, 48 PastelGreen = 119, 221, 119 PattensBlue = 219, 233, 244 Peach = 255, 203, 164 PeachOrange = 255, 204, 153 PeachPuff = 255, 218, 185 PeachYellow = 250, 223, 173 Pear = 209, 226, 49 PearlLusta = 234, 224, 200 Pelorous = 42, 143, 189 Perano = 172, 172, 230 Periwinkle = 197, 203, 225 PersianBlue = 34, 67, 182 PersianGreen = 0, 166, 147 PersianIndigo = 51, 0, 102 PersianPink = 247, 127, 190 PersianRed = 192, 54, 44 PersianRose = 233, 54, 167 Persimmon = 236, 88, 0 Peru = 205, 133, 63 Pesto = 128, 117, 50 PictonBlue = 102, 153, 204 PigmentGreen = 0, 173, 67 PigPink = 255, 218, 233 PineGreen = 1, 121, 111 PineTree = 42, 47, 35 Pink = 255, 192, 203 PinkFlare = 191, 175, 178 PinkLace = 240, 211, 220 PinkSwan = 179, 179, 179 Plum = 221, 160, 221 Pohutukawa = 102, 12, 33 PoloBlue = 119, 158, 203 Pompadour = 129, 20, 83 Portage = 146, 161, 207 PotPourri = 241, 221, 207 PottersClay = 132, 86, 60 PowderBlue = 176, 224, 230 Prim = 228, 196, 207 PrussianBlue = 0, 58, 108 PsychedelicPurple = 223, 0, 255 Puce = 204, 136, 153 Pueblo = 108, 46, 31 PuertoRico = 67, 179, 174 Pumpkin = 255, 99, 28 Purple = 128, 0, 128 PurpleMountainsMajesty = 150, 123, 182 PurpleTaupe = 93, 57, 84 QuarterSpanishWhite = 230, 224, 212 Quartz = 220, 208, 255 Quincy = 106, 84, 69 RacingGreen = 26, 36, 33 RadicalRed = 255, 32, 82 Rajah = 251, 171, 96 RawUmber = 123, 63, 0 RazzleDazzleRose = 254, 78, 218 Razzmatazz = 215, 10, 83 Red = 255, 0, 0 RedBerry = 132, 22, 23 RedDamask = 203, 109, 81 RedOxide = 99, 15, 15 RedRobin = 128, 64, 64 RichBlue = 84, 90, 167 Riptide = 141, 217, 204 RobinsEggBlue = 0, 204, 204 RobRoy = 225, 169, 95 RockSpray = 171, 56, 31 RomanCoffee = 131, 105, 83 RoseBud = 246, 164, 148 RoseBudCherry = 135, 50, 96 RoseTaupe = 144, 93, 93 RosyBrown = 188, 143, 143 Rouge = 176, 48, 96 RoyalBlue = 65, 105, 225 RoyalHeath = 168, 81, 110 RoyalPurple = 102, 51, 152 Ruby = 215, 24, 104 Russet = 128, 70, 27 Rust = 192, 64, 0 RusticRed = 72, 6, 7 Saddle = 99, 81, 71 SaddleBrown = 139, 69, 19 SafetyOrange = 255, 102, 0 Saffron = 244, 196, 48 Sage = 143, 151, 121 Sail = 161, 202, 241 Salem = 0, 133, 67 Salmon = 250, 128, 114 SandyBeach = 253, 213, 177 SandyBrown = 244, 164, 96 Sangria = 134, 1, 17 SanguineBrown = 115, 54, 53 SanMarino = 80, 114, 167 SanteFe = 175, 110, 77 Sapphire = 6, 42, 120 Saratoga = 84, 90, 44 Scampi = 102, 102, 153 Scarlet = 255, 36, 0 ScarletGum = 67, 28, 83 SchoolBusYellow = 255, 216, 0 Schooner = 139, 134, 128 ScreaminGreen = 102, 255, 102 Scrub = 59, 60, 54 SeaBuckthorn = 249, 146, 69 SeaGreen = 46, 139, 87 Seagull = 140, 190, 214 SealBrown = 61, 12, 2 Seance = 96, 47, 107 SeaPink = 215, 131, 127 SeaShell = 255, 245, 238 Selago = 250, 230, 250 SelectiveYellow = 242, 180, 0 SemiSweetChocolate = 107, 68, 35 Sepia = 150, 90, 62 Serenade = 255, 233, 209 Shadow = 133, 109, 77 Shakespeare = 114, 160, 193 Shalimar = 252, 255, 164 Shamrock = 68, 215, 168 ShamrockGreen = 0, 153, 102 SherpaBlue = 0, 75, 73 SherwoodGreen = 27, 77, 62 Shilo = 222, 165, 164 ShipCove = 119, 139, 165 Shocking = 241, 156, 187 ShockingPink = 255, 29, 206 ShuttleGrey = 84, 98, 111 Sidecar = 238, 224, 177 Sienna = 160, 82, 45 Silk = 190, 164, 147 Silver = 192, 192, 192 SilverChalice = 175, 177, 174 SilverTree = 102, 201, 146 SkyBlue = 135, 206, 235 SlateBlue = 106, 90, 205 SlateGray = 112, 128, 144 SlateGrey = 112, 128, 144 Smalt = 0, 48, 143 SmaltBlue = 74, 100, 108 Snow = 255, 250, 250 SoftAmber = 209, 190, 168 Solitude = 235, 236, 240 Sorbus = 233, 105, 44 Spectra = 53, 101, 77 SpicyMix = 136, 101, 78 Spray = 126, 212, 230 SpringBud = 150, 255, 0 SpringGreen = 0, 255, 127 SpringSun = 236, 235, 189 SpunPearl = 170, 169, 173 Stack = 130, 142, 132 SteelBlue = 70, 130, 180 Stiletto = 137, 63, 69 Strikemaster = 145, 92, 131 StTropaz = 50, 82, 123 Studio = 115, 79, 150 Sulu = 201, 220, 135 SummerSky = 33, 171, 205 Sun = 237, 135, 45 Sundance = 197, 179, 88 Sunflower = 228, 208, 10 Sunglow = 255, 204, 51 SunsetOrange = 253, 82, 64 SurfieGreen = 0, 116, 116 Sushi = 111, 153, 64 SuvaGrey = 140, 140, 140 Swamp = 35, 43, 43 SweetCorn = 253, 219, 109 SweetPink = 243, 153, 152 Tacao = 236, 177, 118 TahitiGold = 235, 97, 35 Tan = 210, 180, 140 Tangaroa = 0, 28, 61 Tangerine = 228, 132, 0 TangerineYellow = 253, 204, 13 Tapestry = 183, 110, 121 Taupe = 72, 60, 50 TaupeGrey = 139, 133, 137 TawnyPort = 102, 66, 77 TaxBreak = 79, 102, 106 TeaGreen = 208, 240, 192 Teak = 176, 141, 87 Teal = 0, 128, 128 TeaRose = 255, 133, 207 Temptress = 60, 20, 33 Tenne = 200, 101, 0 TerraCotta = 226, 114, 91 Thistle = 216, 191, 216 TickleMePink = 245, 111, 161 Tidal = 232, 244, 140 TitanWhite = 214, 202, 221 Toast = 165, 113, 100 Tomato = 255, 99, 71 TorchRed = 255, 3, 62 ToryBlue = 54, 81, 148 Tradewind = 110, 174, 161 TrendyPink = 133, 96, 136 TropicalRainForest = 0, 127, 102 TrueV = 139, 114, 190 TulipTree = 229, 183, 59 Tumbleweed = 222, 170, 136 Turbo = 255, 195, 36 TurkishRose = 152, 119, 123 Turquoise = 64, 224, 208 TurquoiseBlue = 118, 215, 234 Tuscany = 175, 89, 62 TwilightBlue = 253, 255, 245 Twine = 186, 135, 89 TyrianPurple = 102, 2, 60 Ultramarine = 10, 17, 149 UltraPink = 255, 111, 255 Valencia = 222, 82, 70 VanCleef = 84, 61, 55 VanillaIce = 229, 204, 201 VenetianRed = 209, 0, 28 Venus = 138, 127, 128 Vermilion = 251, 79, 20 VeryLightGrey = 207, 207, 207 VidaLoca = 94, 140, 49 Viking = 71, 171, 204 Viola = 180, 131, 149 ViolentViolet = 50, 23, 77 Violet = 238, 130, 238 VioletRed = 255, 57, 136 Viridian = 64, 130, 109 VistaBlue = 159, 226, 191 VividViolet = 127, 62, 152 WaikawaGrey = 83, 104, 149 Wasabi = 150, 165, 60 Watercourse = 0, 106, 78 Wedgewood = 67, 107, 149 WellRead = 147, 61, 65 Wewak = 255, 152, 153 Wheat = 245, 222, 179 Whiskey = 217, 154, 108 WhiskeySour = 217, 144, 88 White = 255, 255, 255 WhiteSmoke = 245, 245, 245 WildRice = 228, 217, 111 WildSand = 229, 228, 226 WildStrawberry = 252, 65, 154 WildWatermelon = 255, 84, 112 WildWillow = 172, 191, 96 Windsor = 76, 40, 130 Wisteria = 191, 148, 228 Wistful = 162, 162, 208 Yellow = 255, 255, 0 YellowGreen = 154, 205, 50 YellowOrange = 255, 174, 66 YourPink = 244, 194, 194 } function Convert-BinaryToIP { <# .SYNOPSIS Converts a binary string to an IP address format. .DESCRIPTION This function takes a binary string as input and converts it to an IP address format. The binary string must be evenly divisible by 8. .PARAMETER Binary The binary string to convert to an IP address format. .EXAMPLE Convert-BinaryToIP -Binary "01000001000000100000000100000001" Output: 65.0.1.1 #> [cmdletBinding()] param( [string] $Binary ) $Binary = $Binary -replace '\s+' if ($Binary.Length % 8) { Write-Warning -Message "Convert-BinaryToIP - Binary string '$Binary' is not evenly divisible by 8." return $Null } [int] $NumberOfBytes = $Binary.Length / 8 $Bytes = @(foreach ($i in 0..($NumberOfBytes - 1)) { try { [System.Convert]::ToByte($Binary.Substring(($i * 8), 8), 2) } catch { Write-Warning -Message "Convert-BinaryToIP - Error converting '$Binary' to bytes. `$i was $i." return $Null } }) return $Bytes -join '.' } function Convert-GenericRightsToFileSystemRights { <# .SYNOPSIS Converts generic rights to file system rights for a given set of original rights. .DESCRIPTION This function maps generic rights to corresponding file system rights based on the provided original rights. .PARAMETER OriginalRights Specifies the original generic rights to be converted to file system rights. .EXAMPLE Convert-GenericRightsToFileSystemRights -OriginalRights GENERIC_READ Converts the generic read rights to file system rights. .NOTES This function is based on the mapping provided in the blog post: https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/ .LINK https://blog.cjwdev.co.uk/2011/06/28/permissions-not-included-in-net-accessrule-filesystemrights-enum/ #> [cmdletBinding()] param( [System.Security.AccessControl.FileSystemRights] $OriginalRights ) Begin { $FileSystemRights = [System.Security.AccessControl.FileSystemRights] $GenericRights = @{ GENERIC_READ = 0x80000000; GENERIC_WRITE = 0x40000000; GENERIC_EXECUTE = 0x20000000; GENERIC_ALL = 0x10000000; FILTER_GENERIC = 0x0FFFFFFF; } $MappedGenericRights = @{ FILE_GENERIC_EXECUTE = $FileSystemRights::ExecuteFile -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::ReadAttributes -bor $FileSystemRights::Synchronize FILE_GENERIC_READ = $FileSystemRights::ReadAttributes -bor $FileSystemRights::ReadData -bor $FileSystemRights::ReadExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_WRITE = $FileSystemRights::AppendData -bor $FileSystemRights::WriteAttributes -bor $FileSystemRights::WriteData -bor $FileSystemRights::WriteExtendedAttributes -bor $FileSystemRights::ReadPermissions -bor $FileSystemRights::Synchronize FILE_GENERIC_ALL = $FileSystemRights::FullControl } } Process { $MappedRights = [System.Security.AccessControl.FileSystemRights]::new() if ($OriginalRights -band $GenericRights.GENERIC_EXECUTE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_EXECUTE } if ($OriginalRights -band $GenericRights.GENERIC_READ) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_READ } if ($OriginalRights -band $GenericRights.GENERIC_WRITE) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_WRITE } if ($OriginalRights -band $GenericRights.GENERIC_ALL) { $MappedRights = $MappedRights -bor $MappedGenericRights.FILE_GENERIC_ALL } (($OriginalRights -bAND $GenericRights.FILTER_GENERIC) -bOR $MappedRights) -as $FileSystemRights } End { } } function Convert-IPToBinary { <# .SYNOPSIS Converts an IPv4 address to binary format. .DESCRIPTION This function takes an IPv4 address as input and converts it to binary format. .PARAMETER IP Specifies the IPv4 address to convert to binary format. .EXAMPLE Convert-IPToBinary -IP "192.168.1.1" Converts the IPv4 address "192.168.1.1" to binary format. .EXAMPLE Convert-IPToBinary -IP "10.0.0.1" Converts the IPv4 address "10.0.0.1" to binary format. #> [cmdletBinding()] param( [string] $IP ) $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)' $IP = $IP.Trim() if ($IP -match "\A${IPv4Regex}\z") { try { return ($IP.Split('.') | ForEach-Object { [System.Convert]::ToString([byte] $_, 2).PadLeft(8, '0') }) -join '' } catch { Write-Warning -Message "Convert-IPToBinary - Error converting '$IP' to a binary string: $_" return $Null } } else { Write-Warning -Message "Convert-IPToBinary - Invalid IP detected: '$IP'. Conversion failed." return $Null } } function ConvertFrom-HTML { <# .SYNOPSIS Converts HTML strings to plain text. .DESCRIPTION This function converts HTML strings to plain text. It can also remove HTML tags if specified. .PARAMETER HTML Specifies the HTML strings to convert. .PARAMETER RemoveTags Indicates whether to remove HTML tags from the input HTML strings. .EXAMPLE ConvertFrom-HTML -HTML "<p>Hello, <b>World</b>!</p>" -RemoveTags This example converts the HTML string "<p>Hello, <b>World</b>!</p>" to plain text and removes the HTML tags. #> [alias('Convert-HTMLToString')] [CmdletBinding()] param( [string[]] $HTML, [switch] $RemoveTags ) foreach ($H in $HTML) { if ($RemoveTags) { $H = $H -replace '<[^>]+>', '' } $H -replace '“', '"' -replace '’', "'" -replace '”', '"' -replace '…', '...' -replace '—', '-' -replace '–', '-' } } function ConvertTo-HkeyUser { <# .SYNOPSIS Converts registry paths based on specified criteria. .DESCRIPTION This function converts registry paths based on the provided HiveDictionary, SubKeys, DictionaryKey, and RegistryPath parameters. .PARAMETER HiveDictionary Specifies the dictionary containing the criteria for converting registry paths. .PARAMETER SubKeys Specifies an array of subkeys to process. .PARAMETER DictionaryKey Specifies the key in the RegistryPath to be replaced. .PARAMETER RegistryPath Specifies the original registry path to be converted. .EXAMPLE ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'AllDomain'; 'Key2' = 'All+Default' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', '.DEFAULT') -DictionaryKey 'Key1' -RegistryPath 'HKLM:\Software\Key1\SubKey' Description: Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys. .EXAMPLE ConvertTo-HkeyUser -HiveDictionary @{ 'Key1' = 'Users'; 'Key2' = 'AllDomain+Other' } -SubKeys @('S-1-5-21-123456789-123456789-123456789-1001', 'Offline_User1') -DictionaryKey 'Key2' -RegistryPath 'HKLM:\Software\Key2\SubKey' Description: Converts the RegistryPath based on the specified criteria in the HiveDictionary for the provided SubKeys. #> [CmdletBinding()] param( [System.Collections.IDictionary] $HiveDictionary, [Array] $SubKeys, [string] $DictionaryKey, [string] $RegistryPath ) $OutputRegistryKeys = foreach ($Sub in $Subkeys) { if ($HiveDictionary[$DictionaryKey] -eq 'All') { if ($Sub -notlike "*_Classes*" -and $Sub -ne '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'All+Default') { if ($Sub -notlike "*_Classes*") { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Default') { if ($Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*")) { if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain+Other+Default') { if (($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") -or $Sub -eq '.DEFAULT') { if (-not $Script:DefaultRegistryMounted) { $Script:DefaultRegistryMounted = Mount-DefaultRegistryPath } if (-not $Script:OfflineRegistryMounted) { $Script:OfflineRegistryMounted = Mount-AllRegistryPath foreach ($Key in $Script:OfflineRegistryMounted.Keys) { $RegistryPath.Replace($DictionaryKey, "Users\$Key") } } if ($Sub -eq '.DEFAULT') { $RegistryPath.Replace($DictionaryKey, "Users\.DEFAULT_USER") } else { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } } elseif ($HiveDictionary[$DictionaryKey] -eq 'AllDomain') { if ($Sub.StartsWith("S-1-5-21") -and $Sub -notlike "*_Classes*") { $RegistryPath.Replace($DictionaryKey, "Users\$Sub") } } elseif ($HiveDictionary[$DictionaryKey] -eq 'Users') { if ($Sub -like "Offline_*") { $Script:OfflineRegistryMounted = Mount-AllRegistryPath -MountUsers $Sub foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { $RegistryPath } } } } } $OutputRegistryKeys | Sort-Object -Unique } function ConvertTo-StringByType { <# .SYNOPSIS Private function to use within ConvertTo-JsonLiteral .DESCRIPTION Private function to use within ConvertTo-JsonLiteral .PARAMETER Value Value to convert to JsonValue .PARAMETER Depth Specifies how many levels of contained objects are included in the JSON representation. The default value is 0. .PARAMETER AsArray Outputs the object in array brackets, even if the input is a single object. .PARAMETER DateTimeFormat Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss" .PARAMETER NumberAsString Provides an alternative serialization option that converts all numbers to their string representation. .PARAMETER BoolAsString Provides an alternative serialization option that converts all bool to their string representation. .PARAMETER PropertyName Uses PropertyNames provided by user (only works with Force) .PARAMETER ArrayJoin Forces any array to be a string regardless of depth level .PARAMETER ArrayJoinString Uses defined string or char for array join. By default it uses comma with a space when used. .PARAMETER Force Forces using property names from first object or given thru PropertyName parameter .EXAMPLE $Value = ConvertTo-StringByType -Value $($Object[$a][$i]) -DateTimeFormat $DateTimeFormat .NOTES General notes #> [cmdletBinding()] param( [Object] $Value, [int] $Depth, [int] $MaxDepth, [string] $DateTimeFormat, [switch] $NumberAsString, [switch] $BoolAsString, [System.Collections.IDictionary] $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $NewLineFormatProperty = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $AdvancedReplace, [System.Text.StringBuilder] $TextBuilder, [string[]] $PropertyName, [switch] $ArrayJoin, [string] $ArrayJoinString, [switch] $Force ) Process { if ($null -eq $Value) { "`"`"" } elseif ($Value -is [string]) { $Value = $Value.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) foreach ($Key in $AdvancedReplace.Keys) { $Value = $Value.Replace($Key, $AdvancedReplace[$Key]) } "`"$Value`"" } elseif ($Value -is [DateTime]) { "`"$($($Value).ToString($DateTimeFormat))`"" } elseif ($Value -is [bool]) { if ($BoolAsString) { "`"$($Value)`"" } else { $Value.ToString().ToLower() } } elseif ($Value -is [System.Collections.IDictionary]) { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { "`"$($Value)`"" } else { $Depth++ $null = $TextBuilder.AppendLine("{") for ($i = 0; $i -lt ($Value.Keys).Count; $i++) { $Property = ([string[]]$Value.Keys)[$i] $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $OutputValue = ConvertTo-StringByType -Value $Value[$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append("$OutputValue") if ($i -ne ($Value.Keys).Count - 1) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } } elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) { if ($ArrayJoin) { $Value = $Value -join $ArrayJoinString $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } else { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { $Value = "$Value".Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } else { $CountInternalObjects = 0 $null = $TextBuilder.Append("[") foreach ($V in $Value) { $CountInternalObjects++ if ($CountInternalObjects -gt 1) { $null = $TextBuilder.Append(',') } if ($Force -and -not $PropertyName) { $PropertyName = $V.PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $V.PSObject.Properties.Name } $OutputValue = ConvertTo-StringByType -Value $V -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -PropertyName $PropertyName -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append($OutputValue) } $null = $TextBuilder.Append("]") } } } elseif ($Value -is [System.Enum]) { "`"$($($Value).ToString())`"" } elseif (($Value | IsNumeric) -eq $true) { $Value = $($Value).ToString().Replace(',', '.') if ($NumberAsString) { "`"$Value`"" } else { $Value } } elseif ($Value -is [PSObject]) { if ($MaxDepth -eq 0 -or $Depth -eq $MaxDepth) { "`"$($Value)`"" } else { $Depth++ $CountInternalObjects = 0 $null = $TextBuilder.AppendLine("{") if ($Force -and -not $PropertyName) { $PropertyName = $Value.PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $Value.PSObject.Properties.Name } foreach ($Property in $PropertyName) { $CountInternalObjects++ if ($CountInternalObjects -gt 1) { $null = $TextBuilder.AppendLine(',') } $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $OutputValue = ConvertTo-StringByType -Value $Value.$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $Depth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -Force:$Force -ArrayJoinString $ArrayJoinString -ArrayJoin:$ArrayJoin.IsPresent $null = $TextBuilder.Append("$OutputValue") } $null = $TextBuilder.Append("}") } } else { $Value = $Value.ToString().Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) "`"$Value`"" } } } function Get-ComputerSMBInfo { <# .SYNOPSIS Retrieves information about SMB shares on a remote computer. .DESCRIPTION This function retrieves information about SMB shares on a remote computer using the NetShareEnum method. .PARAMETER ComputerName Specifies the name of the remote computer to retrieve SMB share information from. .PARAMETER Name Specifies an array of share names to filter the results. If not specified, all shares will be retrieved. .PARAMETER SkipDiskSpace Indicates whether to skip retrieving disk space information for each share. .EXAMPLE Get-ComputerSMBInfo -ComputerName "Server01" Description: Retrieves all SMB share information from the remote computer "Server01". .EXAMPLE Get-ComputerSMBInfo -ComputerName "Server01" -Name "Data" Description: Retrieves SMB share information for the share named "Data" on the remote computer "Server01". .NOTES File Name : Get-ComputerSMBInfo.ps1 Prerequisite : This function requires administrative privileges on the remote computer. #> [cmdletbinding()] param( [string] $ComputerName, [string[]] $Name, [switch] $SkipDiskSpace #, # [System.Management.Automation.PSCmdlet]$PSC ) $buffer = [IntPtr]::Zero $read = 0 $total = 0 $resume = 0 $res = [Win32Share.NativeMethods]::NetShareEnum( $ComputerName, 1, [ref]$buffer, ([UInt32]"0xFFFFFFFF"), [ref]$read, [ref]$total, [ref]$resume ) if ($res -ne 0) { $exp = [System.ComponentModel.Win32Exception]$res $er = [System.Management.Automation.ErrorRecord]::new( $exp, 'Win32Share.NativeMethods.GetSmbInf.RemoteException', [System.Management.Automation.ErrorCategory]::NotSpecified, $ComputerName ) $er.ErrorDetails = "Failed to enum share for '$ComputerName': $($exp.Message)" if ($ErrorActionPreference -eq 'Stop') { Write-Error -ErrorRecord $er } else { Write-Warning -Message "Get-ComputerSMBShareList - Failed to enumarate share on '$ComputerName'." } return } try { $entryPtr = $buffer for ($i = 0; $i -lt $total; $i++) { $shareInfo = [System.Runtime.InteropServices.Marshal]::PtrToStructure($entryPtr, [Type]([Win32Share.NativeHelpers+SHARE_INFO_1])) $netNm = $shareInfo.shi1_netname if ($Name) { $isLike = $false foreach ($nm in $Name) { if ($netNm -like $nm) { $isLike = $true continue } } if (-not $isLike) { $entryPtr = [IntPtr]::Add($entryPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($shareInfo)) continue } } $shTyp = $shareInfo.shi1_type $shrPath = "\\$ComputerName\$netNm\" if (-not $SkipDiskSpace) { $freeBytesAvailableToCaller = 0 [System.Nullable[UInt64]]$freeBytesAvailableToCallerNull = $null $totalNumberOfBytes = 0 [System.Nullable[UInt64]]$totalNumberOfBytesNull = $null $totalNumberOfFreeBytes = 0 [System.Nullable[UInt64]]$totalNumberOfFreeBytesNull = $null $lastWin32Error = 0 if (($shTyp -bor [Win32Share.ShareType]::Disk) -eq [Win32Share.ShareType]::Disk) { $dskRes = [Win32Share.NativeMethods]::GetDiskFreeSpaceEx( $shrPath, [ref]$freeBytesAvailableToCaller, [ref]$totalNumberOfBytes, [ref]$totalNumberOfFreeBytes ) if ($dskRes) { $freeBytesAvailableToCallerNull = $freeBytesAvailableToCaller $totalNumberOfBytesNull = $totalNumberOfBytes $totalNumberOfFreeBytesNull = $totalNumberOfFreeBytes } else { $lastWin32Error = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() $exp = [System.ComponentModel.Win32Exception]$lastWin32Error $er = [System.Management.Automation.ErrorRecord]::new( $exp, 'Win32Share.NativeMethods.GetSmbInf.ShareException', [System.Management.Automation.ErrorCategory]::NotSpecified, $shrPath ) $er.ErrorDetails = "Failed to get disk space on '$shrPath' for '$ComputerName': $($exp.Message)" if ($ErrorActionPreference -eq 'Stop') { Write-Error -ErrorRecord $er } else { Write-Warning -Message "Get-ComputerSMBShareList - Failed to get disk space on '$shrPath' for '$ComputerName': $($exp.Message)" } } } [PSCustomObject]@{ PSTypeName = 'Win32Share.NativeMethods' ComputerName = $ComputerName Path = $shrPath Name = $netNm Type = $shTyp Remark = $shareInfo.shi1_remark TotalBytes = $totalNumberOfBytesNull TotalFreeBytes = $totalNumberOfFreeBytesNull FreeBytesAvailableToUser = $freeBytesAvailableToCallerNull } } else { [PSCustomObject]@{ PSTypeName = 'Win32Share.NativeMethods' ComputerName = $ComputerName Path = $shrPath Name = $netNm Type = $shTyp Remark = $shareInfo.shi1_remark } } $entryPtr = [IntPtr]::Add($entryPtr, [System.Runtime.InteropServices.Marshal]::SizeOf($shareInfo)) } } finally { $null = [Win32Share.NativeMethods]::NetApiBufferFree($buffer) } } function Get-ComputerSplit { <# .SYNOPSIS This function splits the list of computer names provided into two arrays: one containing remote computers and another containing the local computer. .DESCRIPTION The Get-ComputerSplit function takes an array of computer names as input and splits them into two arrays based on whether they are remote computers or the local computer. It determines the local computer by comparing the provided computer names with the local computer name and DNS name. .PARAMETER ComputerName Specifies an array of computer names to split into remote and local computers. .EXAMPLE Get-ComputerSplit -ComputerName "Computer1", "Computer2", $Env:COMPUTERNAME This example splits the computer names "Computer1" and "Computer2" into the remote computers array and the local computer array based on the local computer's name. #> [CmdletBinding()] param( [string[]] $ComputerName ) if ($null -eq $ComputerName) { $ComputerName = $Env:COMPUTERNAME } try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Env:COMPUTERNAME } $ComputersLocal = $null [Array] $Computers = foreach ($Computer in $ComputerName) { if ($Computer -eq '' -or $null -eq $Computer) { $Computer = $Env:COMPUTERNAME } if ($Computer -ne $Env:COMPUTERNAME -and $Computer -ne $LocalComputerDNSName) { $Computer } else { $ComputersLocal = $Computer } } , @($ComputersLocal, $Computers) } function Get-IPRange { <# .SYNOPSIS Generates a list of IP addresses within a specified binary range. .DESCRIPTION This function takes two binary strings representing the start and end IP addresses and generates a list of IP addresses within that range. .PARAMETER StartBinary Specifies the starting IP address in binary format. .PARAMETER EndBinary Specifies the ending IP address in binary format. .EXAMPLE Get-IPRange -StartBinary '11000000' -EndBinary '11000010' Description: Generates a list of IP addresses between '192.0.0.0' and '192.0.2.0'. .EXAMPLE Get-IPRange -StartBinary '10101010' -EndBinary '10101100' Description: Generates a list of IP addresses between '170.0.0.0' and '172.0.0.0'. #> [cmdletBinding()] param( [string] $StartBinary, [string] $EndBinary ) [int64] $StartInt = [System.Convert]::ToInt64($StartBinary, 2) [int64] $EndInt = [System.Convert]::ToInt64($EndBinary, 2) for ($BinaryIP = $StartInt; $BinaryIP -le $EndInt; $BinaryIP++) { Convert-BinaryToIP ([System.Convert]::ToString($BinaryIP, 2).PadLeft(32, '0')) } } function Get-LocalComputerSid { <# .SYNOPSIS Get the SID of the local computer. .DESCRIPTION Get the SID of the local computer. .EXAMPLE Get-LocalComputerSid .NOTES General notes #> [cmdletBinding()] param() try { Add-Type -AssemblyName System.DirectoryServices.AccountManagement $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Machine) $UserPrincipal = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($PrincipalContext) $Searcher = [System.DirectoryServices.AccountManagement.PrincipalSearcher]::new() $Searcher.QueryFilter = $UserPrincipal $User = $Searcher.FindAll() foreach ($U in $User) { if ($U.Sid.Value -like "*-500") { return $U.Sid.Value.TrimEnd("-500") } } } catch { Write-Warning -Message "Get-LocalComputerSid - Error: $($_.Exception.Message)" } } function Get-OfflineRegistryProfilesPath { <# .SYNOPSIS Retrieves the paths of offline user profiles in the Windows registry. .DESCRIPTION This function retrieves the paths of offline user profiles in the Windows registry by comparing the profiles listed in 'HKEY_USERS' with those in 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList'. It then checks for the existence of the 'NTUSER.DAT' file for each profile and returns the paths of offline profiles found. .EXAMPLE Get-OfflineRegistryProfilesPath Retrieves the paths of offline user profiles in the Windows registry and returns a hashtable containing the profile paths. .NOTES Name Value ---- ----- Przemek {[FilePath, C:\Users\Przemek\NTUSER.DAT], [Status, ]} test.1 {[FilePath, C:\Users\test.1\NTUSER.DAT], [Status, ]} #> [CmdletBinding()] param( ) $Profiles = [ordered] @{} $CurrentMapping = (Get-PSRegistry -RegistryPath 'HKEY_USERS' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $UsersInSystem = (Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ExpandEnvironmentNames -DoNotUnmount).PSSubKeys $MissingProfiles = foreach ($Profile in $UsersInSystem) { if ($Profile.StartsWith("S-1-5-21") -and $CurrentMapping -notcontains $Profile) { Get-PSRegistry -RegistryPath "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$Profile" -ExpandEnvironmentNames -DoNotUnmount } } foreach ($Profile in $MissingProfiles) { $PathToNTUser = [io.path]::Combine($Profile.ProfileImagePath, 'NTUSER.DAT') $ProfileName = [io.path]::GetFileName($Profile.ProfileImagePath) $StartPath = "Offline_$ProfileName" try { $PathExists = Test-Path -LiteralPath $PathToNTUser -ErrorAction Stop if ($PathExists) { $Profiles[$StartPath] = [ordered] @{ FilePath = $PathToNTUser Status = $null } } } catch { Write-Warning -Message "Mount-OfflineRegistryPath - Couldn't execute. Error: $($_.Exception.Message)" continue } } $Profiles } function Get-PrivateRegistryTranslated { <# .SYNOPSIS Retrieves translated private registry information based on the provided parameters. .DESCRIPTION This function retrieves translated private registry information based on the specified RegistryPath, HiveDictionary, ReverseTypesDictionary, Type, Key, and Value parameters. .PARAMETER RegistryPath Specifies the array of registry paths to be translated. .PARAMETER HiveDictionary Specifies the dictionary containing mappings of registry hives. .PARAMETER ReverseTypesDictionary Specifies the dictionary containing mappings of registry value types. .PARAMETER Type Specifies the type of the registry value. Valid values are 'REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring'. .PARAMETER Key Specifies the key associated with the registry value. .PARAMETER Value Specifies the value of the registry key. .EXAMPLE Get-PrivateRegistryTranslated -RegistryPath "HKLM\Software\Microsoft" -HiveDictionary @{"HKLM"="HKEY_LOCAL_MACHINE"} -ReverseTypesDictionary @{"string"="REG_SZ"} -Type "string" -Key "Version" -Value "10.0.19041" Description ----------- Retrieves translated registry information for the specified registry path. .EXAMPLE Get-PrivateRegistryTranslated -RegistryPath "HKCU\Software\Settings" -HiveDictionary @{"HKCU"="HKEY_CURRENT_USER"} -ReverseTypesDictionary @{"dword"="REG_DWORD"} -Type "dword" -Key "SettingA" -Value 1 Description ----------- Retrieves translated registry information for the specified registry path. #> [cmdletBinding()] param( [Array] $RegistryPath, [System.Collections.IDictionary] $HiveDictionary, [System.Collections.IDictionary] $ReverseTypesDictionary, [Parameter()][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type, [Parameter()][string] $Key, [Parameter()][object] $Value ) foreach ($Registry in $RegistryPath) { if ($Registry -is [string]) { $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") } else { $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") } foreach ($Hive in $HiveDictionary.Keys) { if ($Registry -is [string] -and $Registry.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) { if ($Hive.Length -eq $Registry.Length) { [ordered] @{ HiveKey = $HiveDictionary[$Hive] SubKeyName = $null ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } else { [ordered] @{ HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.substring($Hive.Length + 1) ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } break } elseif ($Registry -isnot [string] -and $Registry.RegistryPath.StartsWith($Hive, [System.StringComparison]::CurrentCultureIgnoreCase)) { if ($Hive.Length -eq $Registry.RegistryPath.Length) { [ordered] @{ ComputerName = $Registry.ComputerName HiveKey = $HiveDictionary[$Hive] SubKeyName = $null ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } else { [ordered] @{ ComputerName = $Registry.ComputerName HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.RegistryPath.substring($Hive.Length + 1) ValueKind = if ($Type) { [Microsoft.Win32.RegistryValueKind]::($ReverseTypesDictionary[$Type]) } else { $null } Key = $Key Value = $Value } } break } } } } function Get-PSConvertSpecialRegistry { <# .SYNOPSIS Converts special registry paths for specified computers. .DESCRIPTION This function converts special registry paths for the specified computers using the provided HiveDictionary. .PARAMETER RegistryPath Specifies the array of registry paths to convert. .PARAMETER Computers Specifies the array of computers to convert registry paths for. .PARAMETER HiveDictionary Specifies the dictionary containing hive keys and their corresponding values. .PARAMETER ExpandEnvironmentNames Indicates whether to expand environment names in the registry paths. .EXAMPLE Get-PSConvertSpecialRegistry -RegistryPath "Users\Offline_Przemek\Software\Policies1\Microsoft\Windows\CloudContent" -Computers "Computer1", "Computer2" -HiveDictionary $HiveDictionary -ExpandEnvironmentNames Converts the specified registry path for the specified computers using the provided HiveDictionary. #> [cmdletbinding()] param( [Array] $RegistryPath, [Array] $Computers, [System.Collections.IDictionary] $HiveDictionary, [switch] $ExpandEnvironmentNames ) $FixedPath = foreach ($R in $RegistryPath) { foreach ($DictionaryKey in $HiveDictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { if ($HiveDictionary[$DictionaryKey] -in 'All', 'All+Default', 'Default', 'AllDomain+Default', 'AllDomain', 'AllDomain+Other', 'AllDomain+Other+Default') { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } elseif ($FirstPart -in 'Users', 'HKEY_USERS', 'HKU' -and $SplitParts[1] -and $SplitParts[1] -like "Offline_*") { foreach ($Computer in $Computers) { $SubKeys = Get-PSRegistry -RegistryPath "HKEY_USERS" -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent -DoNotUnmount if ($SubKeys.PSSubKeys) { $RegistryKeys = ConvertTo-HKeyUser -SubKeys ($SubKeys.PSSubKeys + $SplitParts[1] | Sort-Object) -HiveDictionary $HiveDictionary -DictionaryKey $DictionaryKey -RegistryPath $R foreach ($S in $RegistryKeys) { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $S Error = $null ErrorMessage = $null } } } else { [PSCustomObject] @{ ComputerName = $Computer RegistryPath = $R Error = $true ErrorMessage = "Couldn't connect to $Computer to list HKEY_USERS" } } } } else { $R } break } } } $FixedPath } function Get-PSSubRegistry { <# .SYNOPSIS Retrieves a subkey from the Windows Registry on a local or remote computer. .DESCRIPTION The Get-PSSubRegistry function retrieves a subkey from the Windows Registry on a local or remote computer. It can be used to access specific registry keys and their values. .PARAMETER Registry Specifies the registry key to retrieve. This parameter should be an IDictionary object containing information about the registry key. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the registry key. This parameter is optional and defaults to the local computer. .PARAMETER Remote Indicates that the registry key should be retrieved from a remote computer. .PARAMETER ExpandEnvironmentNames Indicates whether environment variable names in the registry key should be expanded. .EXAMPLE Get-PSSubRegistry -Registry $Registry -ComputerName "RemoteComputer" -Remote Retrieves a subkey from the Windows Registry on a remote computer named "RemoteComputer". .EXAMPLE Get-PSSubRegistry -Registry $Registry -ExpandEnvironmentNames Retrieves a subkey from the Windows Registry on the local computer with expanded environment variable names. #> [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($Registry.Key, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($Registry.Key) } PSType = $SubKey.GetValueKind($Registry.Key) } } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSPath = $Registry.Registry PSKey = $Registry.Key PSValue = $null PSType = $null } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryComplete { <# .SYNOPSIS Retrieves sub-registry information from a specified registry key. .DESCRIPTION This function retrieves sub-registry information from a specified registry key on a local or remote computer. .PARAMETER Registry Specifies the registry key information to retrieve. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the registry information. .PARAMETER Remote Indicates whether the registry key is located on a remote computer. .PARAMETER Advanced Indicates whether to retrieve advanced registry information. .PARAMETER ExpandEnvironmentNames Indicates whether to expand environment variable names in the registry. .EXAMPLE Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer01" -Remote -Advanced Retrieves advanced sub-registry information from the specified registry key on a remote computer named "Computer01". .EXAMPLE Get-PSSubRegistryComplete -Registry $Registry -ComputerName "Computer02" Retrieves sub-registry information from the specified registry key on a local computer named "Computer02". #> [cmdletBinding()] param( [System.Collections.IDictionary] $Registry, [string] $ComputerName, [switch] $Remote, [switch] $Advanced, [switch] $ExpandEnvironmentNames ) if ($Registry.ComputerName) { if ($Registry.ComputerName -ne $ComputerName) { return } } if (-not $Registry.Error) { try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($Registry.HiveKey, $ComputerName, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($Registry.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) } } else { $PSConnection = $false $PSError = $($Registry.ErrorMessage) } if ($PSError) { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError PSSubKeys = $null PSPath = $Registry.Registry PSKey = $Registry.Key } } else { try { $SubKey = $BaseHive.OpenSubKey($Registry.SubKeyName, $false) if ($null -ne $SubKey) { $Object = [ordered] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $false PSErrorMessage = $null PSSubKeys = $SubKey.GetSubKeyNames() PSPath = $Registry.Registry } $Keys = $SubKey.GetValueNames() foreach ($K in $Keys) { if ($K -eq "") { if ($Advanced) { $Object['DefaultKey'] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object['DefaultKey'] = $SubKey.GetValue($K) } } else { if ($Advanced) { $Object[$K] = [ordered] @{ Value = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } Type = $SubKey.GetValueKind($K) } } else { $Object[$K] = if (-not $ExpandEnvironmentNames) { $SubKey.GetValue($K, $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) } else { $SubKey.GetValue($K) } } } } [PSCustomObject] $Object } else { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = "Registry path $($Registry.Registry) doesn't exists." PSSubKeys = $null PSPath = $Registry.Registry } } } catch { [PSCustomObject] @{ PSComputerName = $ComputerName PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message PSSubKeys = $null PSPath = $Registry.Registry } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Get-PSSubRegistryTranslated { <# .SYNOPSIS Retrieves the translated sub-registry information based on the provided RegistryPath, HiveDictionary, and Key. .DESCRIPTION This function retrieves the translated sub-registry information by matching the RegistryPath with the HiveDictionary. It returns an ordered hashtable with details such as Registry, HiveKey, SubKeyName, Key, Error, and ErrorMessage. .PARAMETER RegistryPath Specifies an array of registry paths to be translated. .PARAMETER HiveDictionary Specifies a dictionary containing mappings of hive names to their corresponding keys. .PARAMETER Key Specifies a string key to be included in the output. .EXAMPLE Get-PSSubRegistryTranslated -RegistryPath "HKLM\Software\Microsoft" -HiveDictionary @{ "HKLM" = "HKEY_LOCAL_MACHINE" } -Key "Version" Retrieves the translated sub-registry information for the specified registry path under HKEY_LOCAL_MACHINE hive with the key "Version". .EXAMPLE Get-PSSubRegistryTranslated -RegistryPath "HKCU\Software\Microsoft" -HiveDictionary @{ "HKCU" = "HKEY_CURRENT_USER" } Retrieves the translated sub-registry information for the specified registry path under HKEY_CURRENT_USER hive without specifying a key. #> [cmdletBinding()] param( [Array] $RegistryPath, [System.Collections.IDictionary] $HiveDictionary, [string] $Key ) foreach ($Registry in $RegistryPath) { if ($Registry -is [string]) { $Registry = $Registry.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry -split "\\" $FirstPart = $FirstPartSplit[0] } else { $Registry.RegistryPath = $Registry.RegistryPath.Replace("\\", "\").Replace("\\", "\").TrimStart("\").TrimEnd("\") $FirstPartSplit = $Registry.RegistryPath -split "\\" $FirstPart = $FirstPartSplit[0] } foreach ($Hive in $HiveDictionary.Keys) { if ($Registry -is [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.Length) { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } else { [ordered] @{ Registry = $Registry HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $null ErrorMessage = $null } } break } elseif ($Registry -isnot [string] -and $FirstPart -eq $Hive) { if ($Hive.Length -eq $Registry.RegistryPath.Length) { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $null Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } else { [ordered] @{ ComputerName = $Registry.ComputerName Registry = $Registry.RegistryPath HiveKey = $HiveDictionary[$Hive] SubKeyName = $Registry.RegistryPath.substring($Hive.Length + 1) Key = if ($Key -eq "") { $null } else { $Key } Error = $Registry.Error ErrorMessage = $Registry.ErrorMessage } } break } } } } function Mount-AllRegistryPath { <# .SYNOPSIS Mounts offline registry paths to specified mount points. .DESCRIPTION This function mounts offline registry paths to specified mount points. It iterates through all offline registry profiles and mounts them to the specified mount point. Optionally, you can specify a specific user profile to mount. .PARAMETER MountPoint Specifies the mount point where the registry paths will be mounted. Default is "HKEY_USERS\". .PARAMETER MountUsers Specifies the user profile to mount. If specified, only the specified user profile will be mounted. .EXAMPLE Mount-AllRegistryPath -MountPoint "HKEY_USERS\" -MountUsers "User1" Mounts the offline registry path of user profile "User1" to the default mount point "HKEY_USERS\". .EXAMPLE Mount-AllRegistryPath -MountPoint "HKEY_LOCAL_MACHINE\SOFTWARE" -MountUsers "User2" Mounts the offline registry path of user profile "User2" to the specified mount point "HKEY_LOCAL_MACHINE\SOFTWARE". #> [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\", [string] $MountUsers ) $AllProfiles = Get-OfflineRegistryProfilesPath foreach ($Profile in $AllProfiles.Keys) { if ($MountUsers) { if ($MountUsers -ne $Profile) { continue } } $WhereMount = "$MountPoint\$Profile".Replace("\\", "\") Write-Verbose -Message "Mount-OfflineRegistryPath - Mounting $WhereMount to $($AllProfiles[$Profile].FilePath)" $AllProfiles[$Profile].Status = Mount-PSRegistryPath -MountPoint $WhereMount -FilePath $AllProfiles[$Profile].FilePath } $AllProfiles } function Mount-DefaultRegistryPath { <# .SYNOPSIS Mounts the default registry path to a specified mount point. .DESCRIPTION This function mounts the default registry path to a specified mount point. If an error occurs during the process, it provides appropriate feedback. .PARAMETER MountPoint Specifies the mount point where the default registry path will be mounted. Default value is "HKEY_USERS\.DEFAULT_USER". .EXAMPLE Mount-DefaultRegistryPath -MountPoint "HKLM:\Software\CustomMountPoint" Mounts the default registry path to the specified custom mount point "HKLM:\Software\CustomMountPoint". .EXAMPLE Mount-DefaultRegistryPath Mounts the default registry path to the default mount point "HKEY_USERS\.DEFAULT_USER". #> [CmdletBinding()] param( [string] $MountPoint = "HKEY_USERS\.DEFAULT_USER" ) $DefaultRegistryPath = Get-PSRegistry -RegistryPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -Key 'Default' -ExpandEnvironmentNames -DoNotUnmount if ($PSError -ne $true) { $PathToNTUser = [io.path]::Combine($DefaultRegistryPath.PSValue, 'NTUSER.DAT') Write-Verbose -Message "Mount-DefaultRegistryPath - Mounting $MountPoint to $PathToNTUser" Mount-PSRegistryPath -MountPoint $MountPoint -FilePath $PathToNTUser } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $PSErrorMessage } else { Write-Warning -Message "Mount-DefaultRegistryPath - Couldn't execute. Error: $PSErrorMessage" } } } function New-PrivateRegistry { <# .SYNOPSIS Creates or updates a private registry key on a local or remote computer. .DESCRIPTION The New-PrivateRegistry function creates or updates a registry key on a specified computer. It can be used to set registry values for a specific hive key, subkey, value name, value data, and value kind. .PARAMETER RegistryValue Specifies the registry value to be set. This should be an IDictionary object containing the hive key, subkey, value name, value data, and value kind. .PARAMETER Computer Specifies the name of the computer where the registry key will be created or updated. .PARAMETER Remote Indicates whether the registry operation should be performed on a remote computer. .EXAMPLE New-PrivateRegistry -RegistryValue @{ HiveKey = 'LocalMachine'; SubKeyName = 'Software\MyApp'; Value = 'Version'; ValueData = '1.0'; ValueKind = 'String' } -Computer 'Server01' Description: Creates a registry key 'Version' with value '1.0' under 'LocalMachine\Software\MyApp' on the local computer 'Server01'. .EXAMPLE New-PrivateRegistry -RegistryValue @{ HiveKey = 'CurrentUser'; SubKeyName = 'Control Panel\Desktop'; Value = 'Wallpaper'; ValueData = 'C:\Wallpapers\image.jpg'; ValueKind = 'String' } -Computer 'Workstation01' -Remote Description: Creates a registry key 'Wallpaper' with value 'C:\Wallpapers\image.jpg' under 'CurrentUser\Control Panel\Desktop' on the remote computer 'Workstation01'. #> [CmdletBinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $RegistryValue, [string] $Computer, [switch] $Remote ) try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "New-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } } if ($PSError) { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" } } } else { try { if ($PSCmdlet.ShouldProcess($Computer, "Creating registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)")) { $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true) if (-not $SubKey) { $SubKeysSplit = $RegistryValue.SubKeyName.Split('\') $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) if (-not $SubKey) { $SubKey = $BaseHive.CreateSubKey($SubKeysSplit[0]) } $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) foreach ($S in $SubKeysSplit | Select-Object -Skip 1) { $SubKey = $SubKey.CreateSubKey($S) } $PSError = $false $PSErrorMessage = $null } else { $PSError = $false $PSErrorMessage = "$($RegistryValue.SubKeyName) already exists." } } else { $PSError = $true $PSErrorMessage = "WhatIf was used. No changes done." } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $PSError PSErrorMessage = $PSErrorMessage Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "New-PSRegistry - Creating registry $RegistryPath on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $($_.Exception.Message) Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" } } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Remove-PrivateRegistry { <# .SYNOPSIS Removes a private registry key on a local or remote computer. .DESCRIPTION The Remove-PrivateRegistry function removes a registry key on a specified computer. It can be used to delete registry keys for a specific hive key, subkey, and key value. .PARAMETER Computer Specifies the name of the computer where the registry key will be removed. .PARAMETER Key Specifies the key value to be removed. .PARAMETER RegistryValue Specifies the registry key information to be removed. This should be an IDictionary object containing the hive key, subkey, and key value. .PARAMETER Remote Indicates whether the registry operation should be performed on a remote computer. .PARAMETER Suppress Suppresses the error message if set to true. .EXAMPLE Remove-PrivateRegistry -Computer 'Server01' -Key 'Version' -RegistryValue @{ HiveKey = 'LocalMachine'; SubKeyName = 'Software\MyApp' } Description: Removes the registry key 'Version' under 'LocalMachine\Software\MyApp' on the local computer 'Server01'. .EXAMPLE Remove-PrivateRegistry -Computer 'Workstation01' -Key 'Wallpaper' -RegistryValue @{ HiveKey = 'CurrentUser'; SubKeyName = 'Control Panel\Desktop' } -Remote Description: Removes the registry key 'Wallpaper' under 'CurrentUser\Control Panel\Desktop' on the remote computer 'Workstation01'. #> [cmdletBinding(SupportsShouldProcess)] param( [string] $Computer, [string] $Key, [System.Collections.IDictionary] $RegistryValue, [switch] $Remote, [switch] $Suppress ) $PSConnection = $null $PSError = $null $PSErrorMessage = $null try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Remove-PSRegistry - Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } } if ($PSError) { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key } } } else { try { if ($Key) { $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true) if ($PSCmdlet.ShouldProcess($Computer, "Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key)")) { if ($SubKey) { $SubKey.DeleteValue($RegistryValue.Key, $true) } } else { $PSError = $true $PSErrorMessage = "WhatIf was used. No changes done." } } else { if ($PSCmdlet.ShouldProcess($Computer, "Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) folder")) { if ($BaseHive) { if ($Recursive) { $BaseHive.DeleteSubKeyTree($RegistryValue.SubKeyName, $true) } else { $BaseHive.DeleteSubKey($RegistryValue.SubKeyName, $true) } } } else { $PSError = $true $PSErrorMessage = "WhatIf was used. No changes done." } } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $PSError PSErrorMessage = $PSErrorMessage Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Remove-PSRegistry - Removing registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) key $($RegistryValue.Key) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key } } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Request-Credentials { <# .SYNOPSIS Requests credentials for authentication purposes. .DESCRIPTION The Request-Credentials function is used to prompt the user for credentials. It provides options to input the username and password directly, read the password from a file, convert the password to a secure string, and handle various error scenarios. .PARAMETER UserName Specifies the username for authentication. .PARAMETER Password Specifies the password for authentication. .PARAMETER AsSecure Indicates whether the password should be converted to a secure string. .PARAMETER FromFile Specifies whether the password should be read from a file. .PARAMETER Output Indicates whether the function should return output in case of errors. .PARAMETER NetworkCredentials Specifies if network credentials are being requested. .PARAMETER Service Specifies the service for which credentials are being requested. .EXAMPLE Request-Credentials -UserName 'JohnDoe' -Password 'P@ssw0rd' -AsSecure Requests credentials for the user 'JohnDoe' with the password 'P@ssw0rd' in a secure format. .EXAMPLE Request-Credentials -FromFile -Password 'C:\Credentials.txt' -Output -Service 'FTP' Reads the password from the file 'C:\Credentials.txt' and returns an error message if the file is unreadable for the FTP service. #> [CmdletBinding()] param( [string] $UserName, [string] $Password, [switch] $AsSecure, [switch] $FromFile, [switch] $Output, [switch] $NetworkCredentials, [string] $Service ) if ($FromFile) { if (($Password -ne '') -and (Test-Path $Password)) { Write-Verbose "Request-Credentials - Reading password from file $Password" $Password = Get-Content -Path $Password } else { if ($Output) { return @{ Status = $false; Output = $Service; Extended = 'File with password unreadable.' } } else { Write-Warning "Request-Credentials - Secure password from file couldn't be read. File not readable. Terminating." return } } } if ($AsSecure) { try { $NewPassword = $Password | ConvertTo-SecureString -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($ErrorMessage -like '*Key not valid for use in specified state*') { if ($Output) { return @{ Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else { Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer." return } } else { if ($Output) { return @{ Status = $false; Output = $Service; Extended = $ErrorMessage } } else { Write-Warning -Message "Request-Credentials - $ErrorMessage" return } } } } else { $NewPassword = $Password } if ($UserName -and $NewPassword) { if ($AsSecure) { $Credentials = New-Object System.Management.Automation.PSCredential($Username, $NewPassword) } else { Try { $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($ErrorMessage -like '*Key not valid for use in specified state*') { if ($Output) { return @{ Status = $false; Output = $Service; Extended = "Couldn't use credentials provided. Most likely using credentials from other user/session/computer." } } else { Write-Warning -Message "Request-Credentials - Couldn't use credentials provided. Most likely using credentials from other user/session/computer." return } } else { if ($Output) { return @{ Status = $false; Output = $Service; Extended = $ErrorMessage } } else { Write-Warning -Message "Request-Credentials - $ErrorMessage" return } } } $Credentials = New-Object System.Management.Automation.PSCredential($Username, $SecurePassword) } } else { if ($Output) { return @{ Status = $false; Output = $Service; Extended = 'Username or/and Password is empty' } } else { Write-Warning -Message 'Request-Credentials - UserName or Password are empty.' return } } if ($NetworkCredentials) { return $Credentials.GetNetworkCredential() } else { return $Credentials } } function Resolve-PrivateRegistry { <# .SYNOPSIS Resolves and standardizes registry paths for consistency and compatibility. .DESCRIPTION The Resolve-PrivateRegistry function resolves and standardizes registry paths to ensure uniformity and compatibility across different systems. It cleans up the paths, converts short hive names to full names, and handles special cases like DEFAULT USER mappings. .PARAMETER RegistryPath Specifies an array of registry paths to be resolved and standardized. .EXAMPLE Resolve-PrivateRegistry -RegistryPath 'Users\.DEFAULT_USER\Software\MyApp' Resolves the registry path 'Users\.DEFAULT_USER\Software\MyApp' to 'HKUD\Software\MyApp' for consistent usage. .EXAMPLE Resolve-PrivateRegistry -RegistryPath 'HKCU\Software\MyApp' Resolves the registry path 'HKCU\Software\MyApp' to 'HKEY_CURRENT_USER\Software\MyApp' for compatibility with standard naming conventions. #> [CmdletBinding()] param( [alias('Path')][string[]] $RegistryPath ) foreach ($R in $RegistryPath) { $R = $R.Replace("\\", "\").Replace("\\", "\") If ($R.StartsWith("Users\.DEFAULT_USER") -or $R.StartsWith('HKEY_USERS\.DEFAULT_USER')) { $R = $R.Replace("Users\.DEFAULT_USER", "HKUD") $R.Replace('HKEY_USERS\.DEFAULT_USER', "HKUD") } elseif ($R -like '*:*') { $Found = $false foreach ($DictionaryKey in $Script:Dictionary.Keys) { $SplitParts = $R.Split("\") $FirstPart = $SplitParts[0] if ($FirstPart -eq $DictionaryKey) { $R -replace $DictionaryKey, $Script:Dictionary[$DictionaryKey] $Found = $true break } } if (-not $Found) { $R.Replace(":", "") } } else { $R } } } function Set-PrivateRegistry { <# .SYNOPSIS Sets a registry value on a local or remote computer. .DESCRIPTION The Set-PrivateRegistry function sets a registry value on a specified computer. It can be used to create new registry keys and values, update existing ones, or delete them. .PARAMETER RegistryValue Specifies the registry value to be set. This parameter should be an IDictionary object containing the following properties: - HiveKey: The registry hive key (e.g., 'LocalMachine', 'CurrentUser'). - SubKeyName: The subkey path where the value will be set. - Key: The name of the registry value. - Value: The data to be stored in the registry value. - ValueKind: The type of data being stored (e.g., String, DWord, MultiString). .PARAMETER Computer Specifies the name of the computer where the registry value will be set. .PARAMETER Remote Indicates that the registry value should be set on a remote computer. .PARAMETER Suppress Suppresses error messages and warnings. .EXAMPLE Set-PrivateRegistry -RegistryValue @{HiveKey='LocalMachine'; SubKeyName='SOFTWARE\MyApp'; Key='Version'; Value='1.0'; ValueKind='String'} -Computer 'Server01' Sets the registry value 'Version' under 'HKEY_LOCAL_MACHINE\SOFTWARE\MyApp' to '1.0' on the local computer 'Server01'. .EXAMPLE Set-PrivateRegistry -RegistryValue @{HiveKey='CurrentUser'; SubKeyName='Environment'; Key='Path'; Value='C:\MyApp'; ValueKind='String'} -Computer 'Server02' -Remote Sets the registry value 'Path' under 'HKEY_CURRENT_USER\Environment' to 'C:\MyApp' on the remote computer 'Server02'. .NOTES File Name : Set-PrivateRegistry.ps1 Prerequisite : This function requires administrative privileges to modify the registry. #> [cmdletBinding(SupportsShouldProcess)] param( [System.Collections.IDictionary] $RegistryValue, [string] $Computer, [switch] $Remote, [switch] $Suppress ) Write-Verbose -Message "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer" if ($RegistryValue.ComputerName) { if ($RegistryValue.ComputerName -ne $Computer) { return } } try { if ($Remote) { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegistryValue.HiveKey, $Computer, 0 ) } else { $BaseHive = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryValue.HiveKey, 0 ) } $PSConnection = $true $PSError = $null } catch { $PSConnection = $false $PSError = $($_.Exception.Message) if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } } if ($PSCmdlet.ShouldProcess($Computer, "Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind)")) { if ($PSError) { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $PSError Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } else { try { $SubKey = $BaseHive.OpenSubKey($RegistryValue.SubKeyName, $true) if (-not $SubKey) { $SubKeysSplit = $RegistryValue.SubKeyName.Split('\') $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) if (-not $SubKey) { $SubKey = $BaseHive.CreateSubKey($SubKeysSplit[0]) } $SubKey = $BaseHive.OpenSubKey($SubKeysSplit[0], $true) foreach ($S in $SubKeysSplit | Select-Object -Skip 1) { $SubKey = $SubKey.CreateSubKey($S) } } if ($RegistryValue.ValueKind -eq [Microsoft.Win32.RegistryValueKind]::MultiString) { $SubKey.SetValue($RegistryValue.Key, [string[]] $RegistryValue.Value, $RegistryValue.ValueKind) } elseif ($RegistryValue.ValueKind -in [Microsoft.Win32.RegistryValueKind]::None, [Microsoft.Win32.RegistryValueKind]::Binary) { $SubKey.SetValue($RegistryValue.Key, [byte[]] $RegistryValue.Value, $RegistryValue.ValueKind) } else { $SubKey.SetValue($RegistryValue.Key, $RegistryValue.Value, $RegistryValue.ValueKind) } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $false PSErrorMessage = $null Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } catch { if ($PSBoundParameters.ErrorAction -eq 'Stop') { if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } throw } else { Write-Warning "Set-PSRegistry - Setting registry $($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName) on $($RegistryValue.Key) to $($RegistryValue.Value) of $($RegistryValue.ValueKind) on $Computer have failed. Error: $($_.Exception.Message.Replace([System.Environment]::NewLine, " "))" } if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = $_.Exception.Message Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } } } else { if (-not $Suppress) { [PSCustomObject] @{ PSComputerName = $Computer PSConnection = $PSConnection PSError = $true PSErrorMessage = if ($PSError) { $PSError } else { "WhatIf used - skipping registry setting" } Path = "$($RegistryValue.HiveKey)\$($RegistryValue.SubKeyName)" Key = $RegistryValue.Key Value = $RegistryValue.Value Type = $RegistryValue.ValueKind } } } if ($null -ne $SubKey) { $SubKey.Close() $SubKey.Dispose() } if ($null -ne $BaseHive) { $BaseHive.Close() $BaseHive.Dispose() } } function Test-IPIsInNetwork { <# .SYNOPSIS Checks if an IP address falls within a specified range defined by binary start and end values. .DESCRIPTION This function compares the binary representation of an IP address with the binary start and end values to determine if the IP address falls within the specified range. .EXAMPLE Test-IPIsInNetwork -IP "192.168.1.10" -StartBinary "11000000101010000000000100000000" -EndBinary "11000000101010000000000111111111" Description: Checks if the IP address 192.168.1.10 falls within the range defined by the binary start and end values. #> [cmdletBinding()] param( [string] $IP, [string] $StartBinary, [string] $EndBinary ) $TestIPBinary = Convert-IPToBinary $IP [int64] $TestIPInt64 = [System.Convert]::ToInt64($TestIPBinary, 2) [int64] $StartInt64 = [System.Convert]::ToInt64($StartBinary, 2) [int64] $EndInt64 = [System.Convert]::ToInt64($EndBinary, 2) if ($TestIPInt64 -ge $StartInt64 -and $TestIPInt64 -le $EndInt64) { return $True } else { return $False } } function Unregister-MountedRegistry { <# .SYNOPSIS Unregisters mounted registry paths. .DESCRIPTION This function unregisters mounted registry paths that were previously mounted using Mount-PSRegistryPath. .EXAMPLE Unregister-MountedRegistry Description: Unregisters all mounted registry paths. #> [CmdletBinding()] param( ) if ($null -ne $Script:DefaultRegistryMounted) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\.DEFAULT_USER" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\.DEFAULT_USER" $Script:DefaultRegistryMounted = $null } if ($null -ne $Script:OfflineRegistryMounted) { foreach ($Key in $Script:OfflineRegistryMounted.Keys) { if ($Script:OfflineRegistryMounted[$Key].Status -eq $true) { Write-Verbose -Message "Unregister-MountedRegistry - Dismounting HKEY_USERS\$Key" $null = Dismount-PSRegistryPath -MountPoint "HKEY_USERS\$Key" } } $Script:OfflineRegistryMounted = $null } } function Add-WinADUserGroups { <# .SYNOPSIS Adds a user to specified Active Directory groups. .DESCRIPTION This function adds a user to the specified Active Directory groups. It retrieves the user's current group memberships and adds the user to the specified groups if they are not already a member. .PARAMETER User The user object to add to the groups. .PARAMETER Groups An array of group names to add the user to. .PARAMETER FieldSearch The field to search for group names. Default is 'Name'. .PARAMETER WhatIf Specifies whether to perform a test run without making any changes. .EXAMPLE Add-WinADUserGroups -User $UserObject -Groups @("Group1", "Group2") Adds the user specified by $UserObject to the groups "Group1" and "Group2". .EXAMPLE Add-WinADUserGroups -User $UserObject -Groups @("Group1", "Group2") -FieldSearch 'SamAccountName' Adds the user specified by $UserObject to the groups "Group1" and "Group2" using 'SamAccountName' for group name search. .NOTES File Name : Add-WinADUserGroups.ps1 Prerequisite : Requires Active Directory module. #> [CmdletBinding()] [alias("Add-ADUserGroups")] param( [parameter(Mandatory = $true)][Object] $User, [string[]] $Groups, [string] $FieldSearch = 'Name', [switch] $WhatIf ) $Object = @() try { $ADgroups = Get-ADPrincipalGroupMembership -Identity $User.DistinguishedName | Where-Object { $_.Name -ne "Domain Users" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } if ($Groups) { foreach ($Group in $Groups) { if ($ADgroups.$FieldSearch -notcontains $Group) { try { if (-not $WhatIf) { Add-ADGroupMember -Identity $Group -Members $User.DistinguishedName -ErrorAction Stop } $Object += @{ Status = $true; Output = $Group; Extended = 'Added to group.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group; Extended = $ErrorMessage } } } else { } } } return $Object } function Convert-ADGuidToSchema { <# .SYNOPSIS Converts Guid to schema properties .DESCRIPTION Converts Guid to schema properties .PARAMETER Guid Guid to Convert to Schema Name .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER DisplayName Return the schema name by display name. By default it returns as Name .EXAMPLE $T2 = '570b9266-bbb3-4fad-a712-d2e3fedc34dd' $T = [guid] '570b9266-bbb3-4fad-a712-d2e3fedc34dd' Convert-ADGuidToSchema -Guid $T Convert-ADGuidToSchema -Guid $T2 .NOTES General notes #> [alias('Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs')] [cmdletbinding()] param( [string] $Guid, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $DisplayName ) if (-not $Script:ADSchemaMap -or -not $Script:ADSchemaMapDisplayName) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADSchemaMap = @{ } $Script:ADSchemaMapDisplayName = @{ } $Script:ADSchemaMapDisplayName['00000000-0000-0000-0000-000000000000'] = 'All' $Script:ADSchemaMap.Add('00000000-0000-0000-0000-000000000000', 'All') Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($S in $Script:StandardRights) { $Script:ADSchemaMap["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.schemaIDGUID).Guid)"] = $S.lDAPDisplayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($S in $Script:ExtendedRightsGuids) { $Script:ADSchemaMap["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.name $Script:ADSchemaMapDisplayName["$(([System.GUID]$S.rightsGUID).Guid)"] = $S.displayName } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADGuidToSchema - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($Guid) { if ($DisplayName) { $Script:ADSchemaMapDisplayName[$Guid] } else { $Script:ADSchemaMap[$Guid] } } else { if ($DisplayName) { $Script:ADSchemaMapDisplayName } else { $Script:ADSchemaMap } } } function Convert-ADSchemaToGuid { <# .SYNOPSIS Converts name of schema properties to guids .DESCRIPTION Converts name of schema properties to guids .PARAMETER SchemaName Schema Name to convert to guid .PARAMETER All Get hashtable of all schema properties and their guids .PARAMETER Domain Domain to query. By default the current domain is used .PARAMETER RootDSE RootDSE to query. By default RootDSE is queried from the domain .PARAMETER AsString Return the guid as a string .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' .EXAMPLE Convert-ADSchemaToGuid -SchemaName 'ms-Exch-MSO-Forward-Sync-Cookie' -AsString .NOTES General notes #> [CmdletBinding()] param( [string] $SchemaName, [string] $Domain, [Microsoft.ActiveDirectory.Management.ADEntity] $RootDSE, [switch] $AsString ) if (-not $Script:ADGuidMap -or -not $Script:ADGuidMapString) { if ($RootDSE) { $Script:RootDSE = $RootDSE } elseif (-not $Script:RootDSE) { if ($Domain) { $Script:RootDSE = Get-ADRootDSE -Server $Domain } else { $Script:RootDSE = Get-ADRootDSE } } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $Script:RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Script:ADGuidMap = [ordered] @{ 'All' = [System.GUID]'00000000-0000-0000-0000-000000000000' } $Script:ADGuidMapString = [ordered] @{ 'All' = '00000000-0000-0000-0000-000000000000' } Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:StandardRights) { $Script:StandardRights = Get-ADObject -SearchBase $Script:RootDSE.schemaNamingContext -LDAPFilter "(schemaidguid=*)" -Properties name, lDAPDisplayName, schemaIDGUID -Server $QueryServer -ErrorAction Stop | Select-Object name, lDAPDisplayName, schemaIDGUID } foreach ($Guid in $Script:StandardRights) { $Script:ADGuidMapString[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID).Guid $Script:ADGuidMap[$Guid.lDAPDisplayName] = ([System.GUID]$Guid.schemaIDGUID) $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.schemaIDGUID) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Schema from $QueryServer took $TimeToExecute" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer" $Time = [System.Diagnostics.Stopwatch]::StartNew() if (-not $Script:ExtendedRightsGuids) { $Script:ExtendedRightsGuids = Get-ADObject -SearchBase $Script:RootDSE.ConfigurationNamingContext -LDAPFilter "(&(objectclass=controlAccessRight)(rightsguid=*))" -Properties name, displayName, lDAPDisplayName, rightsGuid -Server $QueryServer -ErrorAction Stop | Select-Object name, displayName, lDAPDisplayName, rightsGuid } foreach ($Guid in $Script:ExtendedRightsGuids) { $Script:ADGuidMapString[$Guid.Name] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMapString[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid).Guid $Script:ADGuidMap[$Guid.Name] = ([System.GUID]$Guid.RightsGuid) $Script:ADGuidMap[$Guid.DisplayName] = ([System.GUID]$Guid.RightsGuid) } $Time.Stop() $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" Write-Verbose "Convert-ADSchemaToGuid - Querying Extended Rights from $QueryServer took $TimeToExecute" } if ($SchemaName) { if ($AsString) { return $Script:ADGuidMapString[$SchemaName] } else { return $Script:ADGuidMap[$SchemaName] } } else { if ($AsString) { $Script:ADGuidMapString } else { $Script:ADGuidMap } } } function Find-ADConnectServer { <# .SYNOPSIS Finds and retrieves information about AD Connect servers based on user descriptions. .DESCRIPTION This function searches for AD Connect servers based on user descriptions containing specific patterns. It extracts server name, tenant name, installation ID, and type of account created. .PARAMETER Description Specifies the user description to search for AD Connect server information. .EXAMPLE Find-ADConnectServer -Description "Account created by John Doe on computer Server1 to tenant Contoso. This is the installation identifier 12345 running on Server1 configured." Retrieves information about the AD Connect server named Server1 with tenant Contoso, installation ID 12345, and account created by John Doe. .NOTES File Name : Find-ADConnectServer.ps1 Prerequisite : Requires Active Directory module. #> [alias('Find-ADSyncServer')] param( ) $Description = Get-ADUser -Filter { Name -like "MSOL*" } -Properties Description | Select-Object Description -ExpandProperty Description foreach ($Desc in $Description) { $PatternType = "(?<=(Account created by ))(.*)(?=(with installation identifier))" $PatternServerName = "(?<=(on computer ))(.*)(?=(configured))" $PatternTenantName = "(?<=(to tenant ))(.*)(?=(. This))" $PatternInstallationID = "(?<=(installation identifier ))(.*)(?=( running on ))" if ($Desc -match $PatternServerName) { $ServerName = ($Matches[0]).Replace("'", '').Replace(' ', '') if ($Desc -match $PatternTenantName) { $TenantName = ($Matches[0]).Replace("'", '').Replace(' ', '') } else { $TenantName = '' } if ($Desc -match $PatternInstallationID) { $InstallationID = ($Matches[0]).Replace("'", '').Replace(' ', '') } else { $InstallationID = '' } if ($Desc -match $PatternType) { $Type = ($Matches[0]).Replace("'", '').Replace('by ', '').Replace('the ', '') } else { $Type = '' } $Data = Get-ADComputer -Identity $ServerName [PSCustomObject] @{ Name = $Data.Name FQDN = $Data.DNSHostName DistinguishedName = $Data.DistinguishedName Type = $Type TenantName = $TenantName InstallatioNID = $InstallationID } } } } function Find-ExchangeServer { <# .SYNOPSIS Find Exchange Servers in Active Directory .DESCRIPTION Find Exchange Servers in Active Directory .EXAMPLE Find-ExchangeServer .NOTES General notes #> [CmdletBinding()] param( ) $ExchangeServers = Get-ADGroup -Identity "Exchange Servers" foreach ($Server in $ExchangeServers) { $Data = Get-ADComputer -Identity $Server.SamAccountName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName [PSCustomObject] @{ Name = $Data.Name FQDN = $Data.DNSHostName OperatingSystem = $Data.OperatingSystem DistinguishedName = $Data.DistinguishedName Enabled = $Data.Enabled } } } function Find-HyperVServer { <# .SYNOPSIS Finds Hyper-V servers in Active Directory. .DESCRIPTION This function retrieves information about Hyper-V servers from Active Directory service connection points. .EXAMPLE Find-HyperVServer Retrieves information about all Hyper-V servers in Active Directory. #> [cmdletbinding()] param() try { $ADObjects = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint" -and Name -eq "Microsoft Hyper-V"' -ErrorAction Stop } catch { Write-Error "Error: $_" } foreach ($Server in $ADObjects) { $Temporary = $Server.DistinguishedName.split(",") $DistinguishedName = $Temporary[1..$Temporary.Count] -join "," $Data = Get-ADComputer -Identity $DistinguishedName -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName [PSCustomObject] @{ Name = $Data.Name FQDN = $Data.DNSHostName OperatingSystem = $Data.OperatingSystem DistinguishedName = $Data.DistinguishedName Enabled = $Data.Enabled } } } function Find-ServerTypes { <# .SYNOPSIS Finds different types of servers in the Active Directory forest. .DESCRIPTION This function retrieves information about different types of servers in the Active Directory forest based on the specified server types. .PARAMETER Type Specifies the type of servers to retrieve. Valid values are 'All', 'ADConnect', 'DomainController', 'Exchange', 'Hyper-V', 'RDSLicense', 'SQL', and 'VirtualMachine'. .EXAMPLE Find-ServerTypes -Type Exchange Retrieves information about Exchange servers in the Active Directory forest. .EXAMPLE Find-ServerTypes -Type DomainController Retrieves information about Domain Controller servers in the Active Directory forest. #> [cmdletbinding()] param( [string[]][ValidateSet('All', 'ADConnect', 'DomainController', 'Exchange', 'Hyper-V', 'RDSLicense', 'SQL', 'VirtualMachine')] $Type = 'All' ) $Forest = Get-ADForest foreach ($Domain in $Forest.Domains) { try { $DomainInformation = Get-ADDomain -Server $Domain -ErrorAction Stop } catch { Write-Warning "Find-ServerTypes - Domain $Domain couldn't be reached. Skipping" continue } try { $ServiceConnectionPoint = Get-ADObject -Filter 'ObjectClass -eq "serviceConnectionPoint"' -ErrorAction Stop -Server $Domain foreach ($Point in $ServiceConnectionPoint) { $Temporary = $Point.DistinguishedName.split(",") $DistinguishedName = $Temporary[1..$Temporary.Count] -join "," $Point | Add-Member -MemberType 'NoteProperty' -Name 'DN' -Value $DistinguishedName -Force } } catch { Write-Error "Find-ServerTypes - Get-ADObject command failed. Terminating. Error $_" return } $ADConnect = Find-ADConnectServer $Computers = Get-ADComputer -Filter * -Properties Name, DNSHostName, OperatingSystem, DistinguishedName, ServicePrincipalName -Server $Domain $Servers = foreach ($Computer in $Computers) { $Services = foreach ($Service in $Computer.servicePrincipalName) { ($Service -split '/')[0] } [PSCustomObject] @{ Name = $Computer.Name FQDN = $Computer.DNSHostName OperatingSystem = $Computer.OperatingSystem DistinguishedName = $Computer.DistinguishedName Enabled = $Computer.Enabled IsExchange = if ($Services -like '*ExchangeMDB*' -or $Services -like '*ExchangeRFR*') { $true } else { $false } IsSql = if ($Services -like '*MSSql*') { $true } else { $false } IsVM = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Windows Virtual Machine') { $true } else { $false } IsHyperV = if ($Services -like '*Hyper-V Replica*') { $true } else { $false } IsSPHyperV = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'Microsoft Hyper-V') { $true } else { $false } IsRDSLicense = if ($ServiceConnectionPoint.DN -eq $Computer.DistinguishedName -and $ServiceConnectionPoint.Name -eq 'TermServLicensing') { $true } else { $false } IsDC = if ($DomainInformation.ReplicaDirectoryServers -contains $Computer.DNSHostName) { $true } else { $false } IsADConnect = if ($ADConnect.FQDN -eq $Computer.DNSHostName) { $true } else { $false } Forest = $Forest.Name Domain = $Domain ServicePrincipalName = ($Services | Sort-Object -Unique) -Join ',' ServiceConnectionPoint = ($ServiceConnectionPoint | Where-Object { $_.DN -eq $Computer.DistinguishedName }).Name -join ',' } } if ($Type -eq 'All') { $Servers } else { if ($Type -contains 'SQL') { $Servers | Where-Object { $_.IsSql -eq $true } } if ($Type -contains 'Exchange' ) { $Servers | Where-Object { $_.IsExchange -eq $true } } if ($Type -contains 'Hyper-V') { $Servers | Where-Object { $_.IsHyperV -eq $true -or $_.IsSPHyperV -eq $true } } if ($Type -contains 'VirtualMachine') { $Servers | Where-Object { $_.IsVM -eq $true } } if ($Type -contains 'RDSLicense') { $Servers | Where-Object { $_.IsRDSLicense -eq $true } } if ($Type -contains 'DomainController') { $Servers | Where-Object { $_.IsDC -eq $true } } if ($Type -contains 'DomainController') { $Servers | Where-Object { $_.IsDC -eq $true } } if ($Type -contains 'ADConnect') { $Servers | Where-Object { $_.IsADConnect -eq $true } } } } } function Find-UsersProxyAddressesStatus { <# .SYNOPSIS This function checks the status of user proxy addresses. .DESCRIPTION The function takes a user object as input and determines the status of their proxy addresses. It checks if the user has any proxy addresses, if the primary proxy address is missing, if there are multiple primary proxy addresses, or if all proxy addresses are correctly set up. .PARAMETER User Specifies the user object for which the proxy addresses status needs to be checked. .EXAMPLE Find-UsersProxyAddressesStatus -User $user Checks the proxy addresses status for the specified user object. .NOTES File Name : Find-UserProxyAddressesStatus.ps1 Prerequisite : This function requires a valid user object with proxy addresses. #> param( $User ) $status = 'No proxy' if ($null -ne $user.proxyAddresses) { $count = 0 foreach ($proxy in $($user.ProxyAddresses)) { if ($proxy.SubString(0, 4) -ceq 'SMTP') { $count++ } } if ($count -eq 0) { $status = 'Missing primary proxy' } elseif ($count -gt 1) { $status = 'Multiple primary proxy' } else { $status = 'All OK' } } else { $status = 'Missing all proxy' } return $status } function Get-ADADministrativeGroups { <# .SYNOPSIS Retrieves administrative groups information from Active Directory. .DESCRIPTION This function retrieves information about administrative groups in Active Directory based on the specified parameters. .PARAMETER Type Specifies the type of administrative groups to retrieve. Valid values are 'DomainAdmins' and 'EnterpriseAdmins'. .PARAMETER Forest Specifies the name of the forest to query for administrative groups. .PARAMETER ExcludeDomains Specifies an array of domains to exclude from the query. .PARAMETER IncludeDomains Specifies an array of domains to include in the query. .PARAMETER ExtendedForestInformation Specifies additional information about the forest to include in the query. .EXAMPLE Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins Output (Where VALUE is Get-ADGroup output): Name Value ---- ----- ByNetBIOS {EVOTEC\Domain Admins, EVOTEC\Enterprise Admins, EVOTECPL\Domain Admins} ad.evotec.xyz {DomainAdmins, EnterpriseAdmins} ad.evotec.pl {DomainAdmins} .NOTES This function requires Active Directory module to be installed on the system. #> [cmdletBinding()] param( [parameter(Mandatory)][validateSet('DomainAdmins', 'EnterpriseAdmins')][string[]] $Type, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) $ADDictionary = [ordered] @{ } $ADDictionary['ByNetBIOS'] = [ordered] @{ } $ADDictionary['BySID'] = [ordered] @{ } $ForestInformation = Get-WinADForestDetails -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation foreach ($Domain in $ForestInformation.Domains) { $ADDictionary[$Domain] = [ordered] @{ } $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer if ($Type -contains 'DomainAdmins') { Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-512'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['DomainAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } foreach ($Domain in $ForestInformation.Forest.Domains) { if (-not $ADDictionary[$Domain]) { $ADDictionary[$Domain] = [ordered] @{ } } if ($Type -contains 'EnterpriseAdmins') { $QueryServer = $ForestInformation['QueryServers'][$Domain]['HostName'][0] $DomainInformation = Get-ADDomain -Server $QueryServer Get-ADGroup -Filter "SID -eq '$($DomainInformation.DomainSID)-519'" -Server $QueryServer -ErrorAction SilentlyContinue | ForEach-Object { $ADDictionary['ByNetBIOS']["$($DomainInformation.NetBIOSName)\$($_.Name)"] = $_ $ADDictionary[$Domain]['EnterpriseAdmins'] = "$($DomainInformation.NetBIOSName)\$($_.Name)" $ADDictionary['BySID'][$_.SID.Value] = $_ } } } return $ADDictionary } function Get-ADEncryptionTypes { <# .SYNOPSIS Retrieves the supported encryption types based on the specified value. .DESCRIPTION This function returns the list of encryption types supported by Active Directory based on the provided value. Each encryption type is represented by a string. .PARAMETER Value Specifies the integer value representing the encryption types to retrieve. .EXAMPLE Get-ADEncryptionTypes -Value 24 Retrieves the following encryption types: - AES128-CTS-HMAC-SHA1-96 - AES256-CTS-HMAC-SHA1-96 .NOTES This function is designed to provide information about encryption types supported by Active Directory. #> [cmdletbinding()] Param( [parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value ) [String[]]$EncryptionTypes = @( Foreach ($V in $Value) { if ([int32]$V -band 0x00000001) { "DES-CBC-CRC" } if ([int32]$V -band 0x00000002) { "DES-CBC-MD5" } if ([int32]$V -band 0x00000004) { "RC4-HMAC" } if ([int32]$V -band 0x00000008) { "AES128-CTS-HMAC-SHA1-96" } if ([int32]$V -band 0x00000010) { "AES256-CTS-HMAC-SHA1-96" } if ([int32]$V -band 0x00000020) { "FAST-supported" } if ([int32]$V -band 0x00000040) { "Compound-identity-supported" } if ([int32]$V -band 0x00000080) { "Claims-supported" } if ([int32]$V -band 0x00000200) { "Resource-SID-compression-disabled" } } ) $EncryptionTypes } function Get-ADTrustAttributes { <# .SYNOPSIS Retrieves and interprets Active Directory trust attributes based on the provided value. .DESCRIPTION This function retrieves and interprets Active Directory trust attributes based on the provided value. It decodes the binary value into human-readable trust attributes. .PARAMETER Value Specifies the integer value representing the trust attributes. .EXAMPLE Get-ADTrustAttributes -Value 1 Retrieves and interprets the trust attributes for the value 1. .EXAMPLE 1, 2, 4 | Get-ADTrustAttributes Retrieves and interprets the trust attributes for the values 1, 2, and 4. .NOTES This function provides a convenient way to decode Active Directory trust attributes. #> [cmdletbinding()] Param( [parameter(Mandatory = $false, ValueFromPipeline = $True)][int32]$Value ) [String[]]$TrustAttributes = @( Foreach ($V in $Value) { if ([int32]$V -band 0x00000001) { "Non Transitive" } if ([int32]$V -band 0x00000002) { "UpLevel Only" } if ([int32]$V -band 0x00000004) { "Quarantined Domain" } if ([int32]$V -band 0x00000008) { "Forest Transitive" } if ([int32]$V -band 0x00000010) { "Cross Organization" } if ([int32]$V -band 0x00000020) { "Within Forest" } if ([int32]$V -band 0x00000040) { "Treat as External" } if ([int32]$V -band 0x00000080) { "Uses RC4 Encryption" } if ([int32]$V -band 0x00000200) { "No TGT DELEGATION" } if ([int32]$V -band 0x00000800) { "Enable TGT DELEGATION" } if ([int32]$V -band 0x00000400) { "PIM Trust" } } ) return $TrustAttributes } function Get-WinADDSAGuid { <# .SYNOPSIS Get DSA GUIDs from a forest for all domain controllers .DESCRIPTION This function retrieves DSA GUIDs from a forest for all domain controllers .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .EXAMPLE Get-WinADDSAGuid | Format-Table .NOTES General notes #> [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [System.Collections.IDictionary] $ExtendedForestInformation ) $Forest = Get-WinADForestDetails -Forest $Forest -ExtendedForestInformation $ExtendedForestInformation -ExcludeDomains $ExcludeDomains -ExcludeDomainControllers $ExcludeDomainControllers -IncludeDomains $IncludeDomains -IncludeDomainControllers $IncludeDomainControllers -SkipRODC:$SkipRODC $ListDSA = [ordered]@{} foreach ($DC in $Forest.ForestDomainControllers) { $ListDSA[$DC.DsaGuid] = [PSCustomObject] @{ Domain = $DC.Domain HostName = $DC.HostName DsaGuid = $DC.DsaGuid DsaGuidName = $DC.DsaGuidName } } if ($Hashtable) { $ListDSA } else { $ListDSA.Values | ForEach-Object { $_ } } } function Get-WinADForestControllers { <# .SYNOPSIS Retrieves information about domain controllers in the specified domain(s). .DESCRIPTION This function retrieves detailed information about domain controllers in the specified domain(s), including hostname, IP addresses, roles, and other relevant details. .PARAMETER TestAvailability Specifies whether to test the availability of domain controllers. .EXAMPLE Get-WinADForestControllers -TestAvailability Tests the availability of domain controllers in the forest. .EXAMPLE Get-WinADDomainControllers Retrieves information about all domain controllers in the forest. .EXAMPLE Get-WinADDomainControllers -Credential $Credential Retrieves information about all domain controllers in the forest using specified credentials. .EXAMPLE Get-WinADDomainControllers | Format-Table * Displays detailed information about all domain controllers in a tabular format. Output: Domain HostName Forest IPV4Address IsGlobalCatalog IsReadOnly SchemaMaster DomainNamingMasterMaster PDCEmulator RIDMaster InfrastructureMaster Comment ------ -------- ------ ----------- --------------- ---------- ------------ ------------------------ ----------- --------- -------------------- ------- ad.evotec.xyz AD1.ad.evotec.xyz ad.evotec.xyz 192.168.240.189 True False True True True True True ad.evotec.xyz AD2.ad.evotec.xyz ad.evotec.xyz 192.168.240.192 True False False False False False False ad.evotec.pl ad.evotec.xyz False False False False False Unable to contact the server. This may be becau... .NOTES This function provides essential information about domain controllers in the forest. #> [alias('Get-WinADDomainControllers')] [CmdletBinding()] param( [string[]] $Domain, [switch] $TestAvailability, [switch] $SkipEmpty, [pscredential] $Credential ) try { if ($Credential) { $Forest = Get-ADForest -Credential $Credential } else { $Forest = Get-ADForest } if (-not $Domain) { $Domain = $Forest.Domains } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning "Get-WinADForestControllers - Couldn't use Get-ADForest feature. Error: $ErrorMessage" return } $Servers = foreach ($D in $Domain) { try { $LocalServer = Get-ADDomainController -Discover -DomainName $D -ErrorAction Stop -Writable if ($Credential) { $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Credential $Credential -Filter * -ErrorAction Stop } else { $DC = Get-ADDomainController -Server $LocalServer.HostName[0] -Filter * -ErrorAction Stop } foreach ($S in $DC) { $Server = [ordered] @{ Domain = $D HostName = $S.HostName Name = $S.Name Forest = $Forest.RootDomain IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly Site = $S.Site SchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') DomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') PDCEmulator = ($S.OperationMasterRoles -contains 'PDCEmulator') RIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') InfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') LdapPort = $S.LdapPort SslPort = $S.SslPort Pingable = $null Comment = '' } if ($TestAvailability) { $Server['Pingable'] = foreach ($_ in $Server.IPV4Address) { Test-Connection -Count 1 -Server $_ -Quiet -ErrorAction SilentlyContinue } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{ Domain = $D HostName = '' Name = '' Forest = $Forest.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' Pingable = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } } if ($SkipEmpty) { return $Servers | Where-Object { $_.HostName -ne '' } } return $Servers } function Get-WinADForestDetails { <# .SYNOPSIS Get details about Active Directory Forest, Domains and Domain Controllers in a single query .DESCRIPTION Get details about Active Directory Forest, Domains and Domain Controllers in a single query .PARAMETER Forest Target different Forest, by default current forest is used .PARAMETER ExcludeDomains Exclude domain from search, by default whole forest is scanned .PARAMETER IncludeDomains Include only specific domains, by default whole forest is scanned .PARAMETER ExcludeDomainControllers Exclude specific domain controllers, by default there are no exclusions, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER IncludeDomainControllers Include only specific domain controllers, by default all domain controllers are included, as long as VerifyDomainControllers switch is enabled. Otherwise this parameter is ignored. .PARAMETER SkipRODC Skip Read-Only Domain Controllers. By default all domain controllers are included. .PARAMETER ExtendedForestInformation Ability to provide Forest Information from another command to speed up processing .PARAMETER Filter Filter for Get-ADDomainController .PARAMETER TestAvailability Check if Domain Controllers are available .PARAMETER Test Pick what to check for availability. Options are: All, Ping, WinRM, PortOpen, Ping+WinRM, Ping+PortOpen, WinRM+PortOpen. Default is All .PARAMETER Ports Ports to check for availability. Default is 135 .PARAMETER PortsTimeout Ports timeout for availability check. Default is 100 .PARAMETER PingCount How many pings to send. Default is 1 .PARAMETER PreferWritable Prefer writable domain controllers over read-only ones when returning Query Servers .PARAMETER Extended Return extended information about domains with NETBIOS names .EXAMPLE Get-WinADForestDetails | Format-Table .EXAMPLE Get-WinADForestDetails -Forest 'ad.evotec.xyz' | Format-Table .NOTES General notes #> [CmdletBinding()] param( [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [string[]] $ExcludeDomainControllers, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [alias('DomainControllers', 'ComputerName')][string[]] $IncludeDomainControllers, [switch] $SkipRODC, [string] $Filter = '*', [switch] $TestAvailability, [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All', [int[]] $Ports = 135, [int] $PortsTimeout = 100, [int] $PingCount = 1, [switch] $PreferWritable, [switch] $Extended, [System.Collections.IDictionary] $ExtendedForestInformation ) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } if (-not $ExtendedForestInformation) { $Findings = [ordered] @{ } try { if ($Forest) { $ForestInformation = Get-ADForest -ErrorAction Stop -Identity $Forest } else { $ForestInformation = Get-ADForest -ErrorAction Stop } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for Forest - $($_.Exception.Message)" return } if (-not $ForestInformation) { return } $Findings['Forest'] = $ForestInformation $Findings['ForestDomainControllers'] = @() $Findings['QueryServers'] = @{ } $Findings['DomainDomainControllers'] = @{ } [Array] $Findings['Domains'] = foreach ($Domain in $ForestInformation.Domains) { if ($IncludeDomains) { if ($Domain -in $IncludeDomains) { $Domain.ToLower() } continue } if ($Domain -notin $ExcludeDomains) { $Domain.ToLower() } } [Array] $DomainsActive = foreach ($Domain in $Findings['Forest'].Domains) { try { $DC = Get-ADDomainController -DomainName $Domain -Discover -ErrorAction Stop -Writable:$PreferWritable.IsPresent $OrderedDC = [ordered] @{ Domain = $DC.Domain Forest = $DC.Forest HostName = [Array] $DC.HostName IPv4Address = $DC.IPv4Address IPv6Address = $DC.IPv6Address Name = $DC.Name Site = $DC.Site } } catch { Write-Warning "Get-WinADForestDetails - Error discovering DC for domain $Domain - $($_.Exception.Message)" continue } if ($Domain -eq $Findings['Forest']['Name']) { $Findings['QueryServers']['Forest'] = $OrderedDC } $Findings['QueryServers']["$Domain"] = $OrderedDC $Domain } [Array] $Findings['Domains'] = foreach ($Domain in $Findings['Domains']) { if ($Domain -notin $DomainsActive) { Write-Warning "Get-WinADForestDetails - Domain $Domain doesn't seem to be active (no DCs). Skipping." continue } $Domain } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { $QueryServer = $Findings['QueryServers'][$Domain]['HostName'][0] [Array] $AllDC = try { try { $DomainControllers = Get-ADDomainController -Filter $Filter -Server $QueryServer -ErrorAction Stop } catch { Write-Warning "Get-WinADForestDetails - Error listing DCs for domain $Domain - $($_.Exception.Message)" continue } foreach ($S in $DomainControllers) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $DSAGuid = (Get-ADObject -Identity $S.NTDSSettingsObjectDN -Server $QueryServer).ObjectGUID $Server = [ordered] @{ Domain = $Domain HostName = $S.HostName Name = $S.Name Forest = $ForestInformation.RootDomain Site = $S.Site IPV4Address = $S.IPV4Address IPV6Address = $S.IPV6Address IsGlobalCatalog = $S.IsGlobalCatalog IsReadOnly = $S.IsReadOnly IsSchemaMaster = ($S.OperationMasterRoles -contains 'SchemaMaster') IsDomainNamingMaster = ($S.OperationMasterRoles -contains 'DomainNamingMaster') IsPDC = ($S.OperationMasterRoles -contains 'PDCEmulator') IsRIDMaster = ($S.OperationMasterRoles -contains 'RIDMaster') IsInfrastructureMaster = ($S.OperationMasterRoles -contains 'InfrastructureMaster') OperatingSystem = $S.OperatingSystem OperatingSystemVersion = $S.OperatingSystemVersion OperatingSystemLong = ConvertTo-OperatingSystem -OperatingSystem $S.OperatingSystem -OperatingSystemVersion $S.OperatingSystemVersion LdapPort = $S.LdapPort SslPort = $S.SslPort DistinguishedName = $S.ComputerObjectDN NTDSSettingsObjectDN = $S.NTDSSettingsObjectDN DsaGuid = $DSAGuid DsaGuidName = "$DSAGuid._msdcs.$($ForestInformation.RootDomain)" Pingable = $null WinRM = $null PortOpen = $null Comment = '' } if ($TestAvailability) { if ($Test -eq 'All' -or $Test -like 'Ping*') { $Server.Pingable = Test-Connection -ComputerName $Server.IPV4Address -Quiet -Count $PingCount } if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Server.WinRM = (Test-WinRM -ComputerName $Server.HostName).Status } if ($Test -eq 'All' -or '*PortOpen*') { $Server.PortOpen = (Test-ComputerPort -Server $Server.HostName -PortTCP $Ports -Timeout $PortsTimeout).Status } } [PSCustomObject] $Server } } catch { [PSCustomObject]@{ Domain = $Domain HostName = '' Name = '' Forest = $ForestInformation.RootDomain IPV4Address = '' IPV6Address = '' IsGlobalCatalog = '' IsReadOnly = '' Site = '' SchemaMaster = $false DomainNamingMasterMaster = $false PDCEmulator = $false RIDMaster = $false InfrastructureMaster = $false LdapPort = '' SslPort = '' DistinguishedName = '' NTDSSettingsObjectDN = '' DsaGuid = '' DsaGuidName = '' Pingable = $null WinRM = $null PortOpen = $null Comment = $_.Exception.Message -replace "`n", " " -replace "`r", " " } } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } if ($null -ne $Findings['DomainDomainControllers'][$Domain]) { [Array] $Findings['DomainDomainControllers'][$Domain] } } if ($Extended) { $Findings['DomainsExtended'] = @{ } $Findings['DomainsExtendedNetBIOS'] = @{ } foreach ($DomainEx in $Findings['Domains']) { try { $Findings['DomainsExtended'][$DomainEx] = Get-ADDomain -Server $Findings['QueryServers'][$DomainEx].HostName[0] | ForEach-Object { [ordered] @{ AllowedDNSSuffixes = $_.AllowedDNSSuffixes | ForEach-Object -Process { $_ } ChildDomains = $_.ChildDomains | ForEach-Object -Process { $_ } ComputersContainer = $_.ComputersContainer DeletedObjectsContainer = $_.DeletedObjectsContainer DistinguishedName = $_.DistinguishedName DNSRoot = $_.DNSRoot DomainControllersContainer = $_.DomainControllersContainer DomainMode = $_.DomainMode DomainSID = $_.DomainSID.Value ForeignSecurityPrincipalsContainer = $_.ForeignSecurityPrincipalsContainer Forest = $_.Forest InfrastructureMaster = $_.InfrastructureMaster LastLogonReplicationInterval = $_.LastLogonReplicationInterval LinkedGroupPolicyObjects = $_.LinkedGroupPolicyObjects | ForEach-Object -Process { $_ } LostAndFoundContainer = $_.LostAndFoundContainer ManagedBy = $_.ManagedBy Name = $_.Name NetBIOSName = $_.NetBIOSName ObjectClass = $_.ObjectClass ObjectGUID = $_.ObjectGUID ParentDomain = $_.ParentDomain PDCEmulator = $_.PDCEmulator PublicKeyRequiredPasswordRolling = $_.PublicKeyRequiredPasswordRolling | ForEach-Object -Process { $_ } QuotasContainer = $_.QuotasContainer ReadOnlyReplicaDirectoryServers = $_.ReadOnlyReplicaDirectoryServers | ForEach-Object -Process { $_ } ReplicaDirectoryServers = $_.ReplicaDirectoryServers | ForEach-Object -Process { $_ } RIDMaster = $_.RIDMaster SubordinateReferences = $_.SubordinateReferences | ForEach-Object -Process { $_ } SystemsContainer = $_.SystemsContainer UsersContainer = $_.UsersContainer } } $NetBios = $Findings['DomainsExtended'][$DomainEx]['NetBIOSName'] $Findings['DomainsExtendedNetBIOS'][$NetBios] = $Findings['DomainsExtended'][$DomainEx] } catch { Write-Warning "Get-WinADForestDetails - Error gathering Domain Information for domain $DomainEx - $($_.Exception.Message)" continue } } } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } $Findings } else { $Findings = Copy-DictionaryManual -Dictionary $ExtendedForestInformation [Array] $Findings['Domains'] = foreach ($_ in $Findings.Domains) { if ($IncludeDomains) { if ($_ -in $IncludeDomains) { $_.ToLower() } continue } if ($_ -notin $ExcludeDomains) { $_.ToLower() } } foreach ($_ in [string[]] $Findings.DomainDomainControllers.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainDomainControllers.Remove($_) } } foreach ($_ in [string[]] $Findings.DomainsExtended.Keys) { if ($_ -notin $Findings.Domains) { $Findings.DomainsExtended.Remove($_) $NetBiosName = $Findings.DomainsExtended.$_.'NetBIOSName' if ($NetBiosName) { $Findings.DomainsExtendedNetBIOS.Remove($NetBiosName) } } } [Array] $Findings['ForestDomainControllers'] = foreach ($Domain in $Findings.Domains) { [Array] $AllDC = foreach ($S in $Findings.DomainDomainControllers["$Domain"]) { if ($IncludeDomainControllers.Count -gt 0) { If (-not $IncludeDomainControllers[0].Contains('.')) { if ($S.Name -notin $IncludeDomainControllers) { continue } } else { if ($S.HostName -notin $IncludeDomainControllers) { continue } } } if ($ExcludeDomainControllers.Count -gt 0) { If (-not $ExcludeDomainControllers[0].Contains('.')) { if ($S.Name -in $ExcludeDomainControllers) { continue } } else { if ($S.HostName -in $ExcludeDomainControllers) { continue } } } $S } if ($SkipRODC) { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC | Where-Object { $_.IsReadOnly -eq $false } } else { [Array] $Findings['DomainDomainControllers'][$Domain] = $AllDC } [Array] $Findings['DomainDomainControllers'][$Domain] } $Findings } } Function Get-WinADForestOptions { <# .SYNOPSIS This Cmdlet gets Active Directory Site Options. .DESCRIPTION This Cmdlet gets Active Directory Site Options. We can fill out the rest of this comment-based help later. .LINK http://myotherpcisacloud.com .LINK https://serverfault.com/questions/543143/detecting-ad-site-options-using-powershell .NOTES Written by Ryan Ries, October 2013. ryanries09@gmail.com. #> [CmdletBinding()] Param( [string] $Domain = $Env:USERDNSDOMAIN ) BEGIN { Add-Type -TypeDefinition @" [System.Flags] public enum nTDSSiteSettingsFlags { NTDSSETTINGS_OPT_IS_AUTO_TOPOLOGY_DISABLED = 0x00000001, NTDSSETTINGS_OPT_IS_TOPL_CLEANUP_DISABLED = 0x00000002, NTDSSETTINGS_OPT_IS_TOPL_MIN_HOPS_DISABLED = 0x00000004, NTDSSETTINGS_OPT_IS_TOPL_DETECT_STALE_DISABLED = 0x00000008, NTDSSETTINGS_OPT_IS_INTER_SITE_AUTO_TOPOLOGY_DISABLED = 0x00000010, NTDSSETTINGS_OPT_IS_GROUP_CACHING_ENABLED = 0x00000020, NTDSSETTINGS_OPT_FORCE_KCC_WHISTLER_BEHAVIOR = 0x00000040, NTDSSETTINGS_OPT_FORCE_KCC_W2K_ELECTION = 0x00000080, NTDSSETTINGS_OPT_IS_RAND_BH_SELECTION_DISABLED = 0x00000100, NTDSSETTINGS_OPT_IS_SCHEDULE_HASHING_ENABLED = 0x00000200, NTDSSETTINGS_OPT_IS_REDUNDANT_SERVER_TOPOLOGY_ENABLED = 0x00000400, NTDSSETTINGS_OPT_W2K3_IGNORE_SCHEDULES = 0x00000800, NTDSSETTINGS_OPT_W2K3_BRIDGES_REQUIRED = 0x00001000 } "@ if ($Domain) { $RootDSE = Get-ADRootDSE -Server $Domain } else { $RootDSE = Get-ADRootDSE } $DomainCN = ConvertFrom-DistinguishedName -DistinguishedName $RootDSE.defaultNamingContext -ToDomainCN $QueryServer = (Get-ADDomainController -DomainName $DomainCN -Discover -ErrorAction Stop).Hostname[0] $Sites = Get-ADObject -Filter 'objectClass -eq "site"' -SearchBase ($RootDSE).ConfigurationNamingContext -Server $QueryServer ForEach ($Site In $Sites) { $SiteSettings = Get-ADObject "CN=NTDS Site Settings,$($Site.DistinguishedName)" -Properties Options -Server $QueryServer If (!$SiteSettings.PSObject.Properties.Match('Options').Count -OR $SiteSettings.Options -EQ 0) { [PSCustomObject]@{ SiteName = $Site.Name DistinguishedName = $Site.DistinguishedName SiteOptions = '(none)' } } Else { [PSCustomObject]@{ SiteName = $Site.Name DistinguishedName = $Site.DistinguishedName SiteOptions = [Enum]::Parse('nTDSSiteSettingsFlags', $SiteSettings.Options) } } } } } function Get-WinADOrganizationalUnitData { <# .SYNOPSIS Retrieves detailed information about Active Directory Organizational Units. .DESCRIPTION This function retrieves detailed information about the specified Active Directory Organizational Units, including properties like CanonicalName, City, Country, Description, and more. .PARAMETER OrganizationalUnit Specifies the Organizational Units to retrieve information for. .EXAMPLE Get-WinADOrganizationalUnitData -OrganizationalUnit 'OU=Users-O365,OU=Production,DC=ad,DC=evotec,DC=xyz' Retrieves information for the specified Organizational Unit 'Users-O365' under 'Production' in the Active Directory domain 'ad.evotec.xyz'. .NOTES Output of function: CanonicalName : ad.evotec.xyz/Production/Users-O365 City : CN : Country : PL Created : 09.11.2018 17:38:32 Description : OU for Synchronization of Users to Office 365 DisplayName : DistinguishedName : OU=Users-O365,OU=Production,DC=ad,DC=evotec,DC=xyz LinkedGroupPolicyObjects : {cn={74D09C6F-35E9-4743-BCF7-F87D7010C60D},cn=policies,cn=system,DC=ad,DC=evotec,DC=xyz} ManagedBy : Modified : 19.11.2018 22:54:47 Name : Users-O365 PostalCode : ProtectedFromAccidentalDeletion : True State : StreetAddress : #> [CmdletBinding()] param( [string[]] $OrganizationalUnit ) $Output = foreach ($OU in $OrganizationalUnit) { $Data = Get-ADOrganizationalUnit -Identity $OU -Properties CanonicalName, City, CN, Country, Created, Description, DisplayName, DistinguishedName, ManagedBy, Modified, Name, OU, PostalCode, ProtectedFromAccidentalDeletion, State, StreetAddress [PsCustomobject][Ordered] @{ CanonicalName = $Data.CanonicalName City = $Data.City CN = $Data.CN Country = $Data.Country Created = $Data.Created Description = $Data.Description DisplayName = $Data.DisplayName DistinguishedName = $Data.DistinguishedName LinkedGroupPolicyObjects = $Data.LinkedGroupPolicyObjects ManagedBy = Get-WinADUsersByDN -DistinguishedName $U.ManagedBy Modified = $Data.Modified Name = $Data.Name PostalCode = $Data.PostalCode ProtectedFromAccidentalDeletion = $Data.ProtectedFromAccidentalDeletion State = $Data.State StreetAddress = $Data.StreetAddress } } return $Output } function Get-WinADOrganizationalUnitFromDN { <# .SYNOPSIS This function extracts the Organizational Unit (OU) from a given Distinguished Name (DN). .DESCRIPTION This function takes a Distinguished Name (DN) as input and returns the Organizational Unit (OU) part of it. .PARAMETER DistinguishedName Specifies the Distinguished Name (DN) from which to extract the Organizational Unit (OU). .EXAMPLE Extract the Organizational Unit (OU) from a Distinguished Name. $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' Get-WinADOrganizationalUnitFromDN -DistinguishedName $DistinguishedName .NOTES This function uses regular expressions to extract the Organizational Unit (OU) from the given Distinguished Name (DN). #> [CmdletBinding()] param( $DistinguishedName ) return [Regex]::Match($DistinguishedName, '(?=OU)(.*\n?)(?<=.)').Value } function Get-WinADUsersByDN { <# .SYNOPSIS Retrieves Active Directory user information based on the provided DistinguishedName(s). .DESCRIPTION This function retrieves Active Directory user information based on the provided DistinguishedName(s). It returns specific user properties for the given DistinguishedName(s). .PARAMETER DistinguishedName Specifies the DistinguishedName(s) of the user(s) to retrieve information for. .PARAMETER Field Specifies the specific field to return for each user. Default value is 'DisplayName'. .PARAMETER All Indicates whether to return all properties of the user(s). .EXAMPLE Get-WinADUsersByDN -DistinguishedName "CN=John Doe,OU=Users,DC=contoso,DC=com" Retrieves the DisplayName of the user with the specified DistinguishedName. .EXAMPLE Get-WinADUsersByDN -DistinguishedName "CN=Jane Smith,OU=Users,DC=contoso,DC=com" -Field "EmailAddress" Retrieves the EmailAddress of the user with the specified DistinguishedName. .EXAMPLE Get-WinADUsersByDN -DistinguishedName "CN=Admin,OU=Users,DC=contoso,DC=com" -All Retrieves all properties of the user with the specified DistinguishedName. #> param( [alias('DN')][string[]]$DistinguishedName, [string] $Field = 'DisplayName', # return field [switch] $All ) $Properties = 'DistinguishedName', 'Enabled', 'GivenName', 'Name', 'SamAccountName', 'SID', 'Surname', 'UserPrincipalName', 'EmailAddress', 'DisplayName' $Users = foreach ($DN in $DistinguishedName) { try { Get-ADUser -Identity $DN -Properties $Properties } catch { } } if ($All) { return $Users } else { return $Users.$Field } } function Get-WinADUsersByOU { <# .SYNOPSIS Retrieves Active Directory users within a specified Organizational Unit. .DESCRIPTION This function retrieves Active Directory users within the specified Organizational Unit. .PARAMETER OrganizationalUnit Specifies the Organizational Unit from which to retrieve users. .EXAMPLE Get-WinADUsersByOU -OrganizationalUnit "OU=Sales,DC=Contoso,DC=com" Retrieves all users within the Sales Organizational Unit in the Contoso domain. #> [CmdletBinding()] param ( $OrganizationalUnit ) $OU = Get-ADOrganizationalUnit $OrganizationalUnit if ($OU.ObjectClass -eq 'OrganizationalUnit') { try { $Users = Get-ADUser -SearchBase $OU -Filter * -Properties $Script:UserProperties } catch { Write-Color @Script:WriteParameters -Text '[i]', ' One or more properties are invalid - Terminating', ' Terminating' -Color Yellow, White, Red return } } return $Users } function Get-WinADUserSnapshot { <# .SYNOPSIS Retrieves a snapshot of Active Directory user information and saves it to an XML file. .DESCRIPTION The Get-WinADUserSnapshot function retrieves detailed information about an Active Directory user and saves it to an XML file specified by the XmlPath parameter. .PARAMETER User Specifies the Active Directory user object for which to retrieve the snapshot. .PARAMETER XmlPath Specifies the path where the XML snapshot file will be saved. .PARAMETER WhatIf Indicates whether the operation should only be simulated without actually saving the snapshot. .EXAMPLE Get-WinADUserSnapshot -User $userObject -XmlPath "C:\Snapshots" -WhatIf Retrieves a snapshot of the user object and simulates saving it to the specified path. .EXAMPLE Get-WinADUserSnapshot -User $userObject -XmlPath "C:\Snapshots" Retrieves a snapshot of the user object and saves it to the specified path. #> [CmdletBinding()] [alias("Get-ADUserSnapshot")] param ( [parameter(Mandatory = $true)][Object] $User, [string] $XmlPath, [switch] $WhatIf ) $Object = @() try { $FullData = Get-ADUser -Identity $User.DistinguishedName -Properties * if (($XmlPath) -and (Test-Path $XmlPath)) { $FullPath = [IO.Path]::Combine($XmlPath, "$($User.SamAccountName).xml") if (-not $WhatIf) { $FullData | Export-Clixml -Path $FullPath -ErrorAction Stop } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Saved to $FullPath" } } else { $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = 'XmlPath Incorrect' } } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } return $Object } function Remove-WinADUserGroups { <# .SYNOPSIS Removes specified Active Directory groups from a user. .DESCRIPTION This function removes specified Active Directory groups from a user account. It provides options to remove groups based on category, scope, or all groups. .PARAMETER User The user object from which groups will be removed. .PARAMETER GroupCategory Specifies the category of groups to remove. Valid values are "Distribution" and "Security". .PARAMETER GroupScope Specifies the scope of groups to remove. Valid values are "DomainLocal", "Global", and "Universal". .PARAMETER Groups An array of specific group names to remove. .PARAMETER All If specified, removes all groups from the user. .PARAMETER WhatIf Shows what would happen if the command runs without actually running it. .EXAMPLE Remove-WinADUserGroups -User $User -All Removes all groups from the specified user account. .EXAMPLE Remove-WinADUserGroups -User $User -GroupCategory "Security" -GroupScope "Global" Removes all security groups with a global scope from the specified user account. #> [CmdletBinding()] [alias("Remove-ADUserGroups")] param( [parameter(Mandatory = $true)][Object] $User, [ValidateSet("Distribution", "Security")][String] $GroupCategory , [ValidateSet("DomainLocal", "Global", "Universal")][String] $GroupScope, [string[]] $Groups, [switch] $All, [switch] $WhatIf ) $Object = @() try { $ADgroups = Get-ADPrincipalGroupMembership -Identity $User.DistinguishedName -ErrorAction Stop | Where-Object { $_.Name -ne "Domain Users" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } if ($ADgroups) { if ($All) { foreach ($Group in $ADgroups) { try { if (-not $WhatIf) { Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop } $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } if ($GroupCategory) { $ADGroupsByCategory = $ADgroups | Where-Object { $_.GroupCategory -eq $GroupCategory } if ($ADGroupsByCategory) { foreach ($Group in $ADGroupsByCategory) { try { if (-not $WhatIf) { Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop } $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } if ($GroupScope) { $ADGroupsByScope = $ADgroups | Where-Object { $_.GroupScope -eq $GroupScope } if ($ADGroupsByScope) { foreach ($Group in $ADGroupsByScope) { try { if (-not $WhatIf) { Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $Group -Confirm:$false -ErrorAction Stop } $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } if ($Groups) { foreach ($Group in $Groups) { $ADGroupsByName = $ADgroups | Where-Object { $_.Name -like $Group } if ($ADGroupsByName) { try { if (-not $WhatIf) { Remove-ADPrincipalGroupMembership -Identity $User.DistinguishedName -MemberOf $ADGroupsByName -Confirm:$false -ErrorAction Stop } $Object += @{ Status = $true; Output = $Group.Name; Extended = 'Removed from group.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } else { $Object += @{ Status = $false; Output = $Group.Name; Extended = 'Not available on user.' } } } } } return $Object } function Set-WinADGroupSynchronization { <# .SYNOPSIS Sets up synchronization between two Active Directory groups. .DESCRIPTION This function sets up synchronization between two Active Directory groups by copying members from one group to another based on specified criteria. .PARAMETER GroupFrom The name of the source Active Directory group from which members will be synchronized. .PARAMETER GroupTo The name of the target Active Directory group to which members will be synchronized. .PARAMETER Type Specifies the type of members to synchronize. Valid values are 'User', 'Group', or 'All'. Default is 'User'. .PARAMETER Recursive Specifies the type of synchronization to perform. Valid values are 'None', 'RecursiveFrom', 'RecursiveBoth', or 'RecursiveTo'. Default is 'None'. .PARAMETER WhatIf Shows what would happen if the synchronization is performed without actually performing it. .EXAMPLE Set-WinADGroupSynchronization -GroupFrom 'GDS-TestGroup1' -GroupTo 'GDS-TestGroup2' -Type 'All' -Recursive None Synchronizes all members from 'GDS-TestGroup1' to 'GDS-TestGroup2' without recursion. .EXAMPLE Set-WinADGroupSynchronization -GroupFrom 'GDS-TestGroup1' -GroupTo 'GDS-TestGroup2' -Type 'User' -Recursive RecursiveBoth Synchronizes only user members from 'GDS-TestGroup1' to 'GDS-TestGroup2' with recursion in both groups. #> [CmdletBinding()] param( [parameter(Mandatory = $true)][string] $GroupFrom, [parameter(Mandatory = $true)][string] $GroupTo, [parameter(Mandatory = $false)][ValidateSet("User", "Group", "All")][string] $Type = 'User', [parameter(Mandatory = $false)][ValidateSet("None", "RecursiveFrom", "RecursiveBoth", "RecursiveTo")] $Recursive = 'None', [switch] $WhatIf ) Begin { $Object = @() if ($Recursive -eq 'None') { $GroupFromRecursive = $false $GroupToRecursive = $false } elseif ($Recursive -eq 'RecursiveFrom') { $GroupFromRecursive = $true $GroupToRecursive = $false } elseif ($Recursive -eq 'RecursiveBoth') { $GroupFromRecursive = $true $GroupToRecursive = $true } else { $GroupFromRecursive = $false $GroupToRecursive = $true } } Process { try { $GroupMembersFrom = Get-ADGroupMember -Identity $GroupFrom -Recursive:$GroupFromRecursive | Select-Object Name, ObjectClass, SamAccountName, UserPrincipalName } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } try { $GroupMembersTo = Get-ADGroupMember -Identity $GroupTo -Recursive:$GroupToRecursive | Select-Object Name, ObjectClass, SamAccountName, UserPrincipalName } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } if ($Object.Count -gt 0) { return $Object } foreach ($User in $GroupMembersFrom) { if ($User.ObjectClass -eq "user") { if ($Type -eq 'User' -or $Type -eq 'All') { if ($GroupMembersTo.SamAccountName -notcontains $User.SamAccountName) { try { if (-not $WhatIf) { Add-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Added to group $GroupTo" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } else { if ($Type -eq 'Group' -or $Type -eq 'All') { if ($GroupMembersTo.SamAccountName -notcontains $User.SamAccountName) { try { if (-not $WhatIf) { Add-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Added to group $GroupTo" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } } foreach ($User in $GroupMembersTo) { if ($User.ObjectClass -eq "user") { if ($Type -eq 'User' -or $Type -eq 'All') { if ($GroupMembersFrom.SamAccountName -notcontains $User.SamAccountName) { Write-Color "Not a member of $GroupFrom - requires removal from $GroupTo ", $User.SamAccountName -Color Red -LogFile $LogFile try { if (-not $WhatIf) { Remove-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName -Confirm:$false } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Removed from group $GroupTo" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } else { if ($Type -eq 'Group' -or $Type -eq 'All') { if ($GroupMembersFrom.SamAccountName -notcontains $User.SamAccountName) { Write-Color "Not a member of $GroupFrom - requires removal from $GroupTo ", $User.SamAccountName -Color Red -LogFile $LogFile try { if (-not $WhatIf) { Remove-ADGroupMember -Identity $GroupTo -Members $User.SamAccountName -Confirm:$false } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Removed from group $GroupTo" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $Group.Name; Extended = $ErrorMessage } } } } } } } End { return $object } } function Set-WinADUserFields { <# .SYNOPSIS Sets specified fields for an Active Directory user with additional options. .DESCRIPTION This function allows setting specified fields for an Active Directory user with the option to add text before or after the existing field value. .PARAMETER User Specifies the Active Directory user object to modify. .PARAMETER Option Specifies whether to add the text before or after the existing field value. Valid values are 'Before' and 'After'. .PARAMETER TextToAdd Specifies the text to add before or after the existing field value. .PARAMETER TextToRemove Specifies the text to remove from the existing field value. .PARAMETER Fields Specifies an array of fields to modify for the user. .PARAMETER WhatIf Indicates that the cmdlet should display what changes would occur without actually making any changes. .EXAMPLE Set-WinADUserFields -User $user -Option 'After' -TextToAdd 'New' -Fields 'Name' Adds the text 'New' after the existing 'Name' field value for the specified user. .EXAMPLE Set-WinADUserFields -User $user -Option 'Before' -TextToAdd 'Old' -Fields 'Description' Adds the text 'Old' before the existing 'Description' field value for the specified user. #> [CmdletBinding()] [alias("Set-ADUserName")] param ( [parameter(Mandatory = $true)][Object] $User, [parameter(Mandatory = $false)][ValidateSet("Before", "After")][String] $Option, [string] $TextToAdd, [string] $TextToRemove, [string[]] $Fields, [switch] $WhatIf ) $Object = @() if ($TextToAdd) { foreach ($Field in $Fields) { if ($User.$Field -notlike "*$TextToAdd*") { if ($Option -eq 'After') { $NewName = "$($User.$Field)$TextToAdd" } elseif ($Option -eq 'Before') { $NewName = "$TextToAdd$($User."$Field")" } if ($NewName -ne $User.$Field) { if ($Field -eq 'Name') { try { if (-not $WhatIf) { Rename-ADObject -Identity $User.DistinguishedName -NewName $NewName } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed account '$Field' to '$NewName'" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } else { $Splat = @{ Identity = $User.DistinguishedName "$Field" = $NewName } try { if (-not $WhatIf) { Set-ADUser @Splat } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed field '$Field' to '$NewName'" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } } } } } if ($TextToRemove) { foreach ($Field in $Fields) { if ($User.$Field -like "*$TextToRemove*") { $NewName = $($User.$Field).Replace($TextToRemove, '') if ($Field -eq 'Name') { try { if (-not $WhatIf) { Rename-ADObject -Identity $User.DistinguishedName -NewName $NewName } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed account '$Field' to '$NewName'" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = "Field: '$Field' Error: '$ErrorMessage'" } } } else { $Splat = @{ Identity = $User.DistinguishedName "$Field" = $NewName } try { if (-not $WhatIf) { Set-ADUser @Splat } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = "Renamed field $Field to $NewName" } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = "Field: $Field Error: $ErrorMessage" } } } } } } return $Object } Function Set-WinADUserSettingGAL { <# .SYNOPSIS Sets the Exchange Global Address List (GAL) visibility for a specified user. .DESCRIPTION This function allows you to hide or show a user in the Global Address List (GAL) of Exchange. It updates the msExchHideFromAddressLists attribute of the user object in Active Directory. .PARAMETER User Specifies the user object for which the GAL visibility needs to be set. .PARAMETER Option Specifies whether to 'Hide' or 'Show' the user in the GAL. .PARAMETER WhatIf Displays what would happen if the command runs without actually running the command. .EXAMPLE Set-WinADUserSettingGAL -User "JohnDoe" -Option "Hide" Hides the user "JohnDoe" from the Global Address List. .EXAMPLE Set-WinADUserSettingGAL -User "JaneSmith" -Option "Show" Shows the user "JaneSmith" in the Global Address List. #> [CmdletBinding()] [alias("Set-ADUserSettingGAL")] param ( [parameter(Mandatory = $true)][Object] $User, [parameter(Mandatory = $true)][ValidateSet("Hide", "Show")][String]$Option, [switch] $WhatIf ) $Object = @() if ($User) { if ($Option -eq 'Hide') { if (-not $User.msExchHideFromAddressLists) { try { if (-not $WhatIf) { Set-ADObject -Identity $User.DistinguishedName -Replace @{msExchHideFromAddressLists = $true } } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Hidden from GAL.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } } elseif ($Option -eq 'Show') { if ($User.msExchHideFromAddressLists) { try { if ($WhatIf) { Set-ADObject -Identity $User.DistinguishedName -Clear msExchHideFromAddressLists } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Unhidden in GAL.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } } } return $Object } function Set-WinADUserStatus { <# .SYNOPSIS Enables or disables a user account in Active Directory. .DESCRIPTION The Set-WinADUserStatus function enables or disables a specified user account in Active Directory based on the provided option. It also provides an option to simulate the action using the WhatIf switch. .PARAMETER User Specifies the user account to enable or disable. .PARAMETER Option Specifies whether to enable or disable the user account. Valid values are "Enable" or "Disable". .PARAMETER WhatIf Indicates that the cmdlet should display what would happen if it were to run, without actually performing any action. .EXAMPLE Set-WinADUserStatus -User $user -Option "Enable" Enables the user account specified by $user in Active Directory. .EXAMPLE Set-WinADUserStatus -User $user -Option "Disable" -WhatIf Displays what would happen if the user account specified by $user were to be disabled in Active Directory, without actually disabling it. #> [CmdletBinding()] [alias("Set-ADUserStatus")] param ( [parameter(Mandatory = $true)][Object] $User, [parameter(Mandatory = $true)][ValidateSet("Enable", "Disable")][String] $Option, [switch] $WhatIf # $WriteParameters ) $Object = @() if ($Option -eq 'Enable' -and $User.Enabled -eq $false) { try { if (-not $WhatIf) { Set-ADUser -Identity $User.DistinguishedName -Enabled $true } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Enabled user.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } elseif ($Option -eq 'Disable' -and $User.Enabled -eq $true) { try { if (-not $WhatIf) { Set-ADUser -Identity $User.DistinguishedName -Enabled $false } $Object += @{ Status = $true; Output = $User.SamAccountName; Extended = 'Disabled user.' } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " $Object += @{ Status = $false; Output = $User.SamAccountName; Extended = $ErrorMessage } } } return $Object } function Get-CimData { <# .SYNOPSIS Helper function for retreiving CIM data from local and remote computers .DESCRIPTION Helper function for retreiving CIM data from local and remote computers .PARAMETER ComputerName Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM). .PARAMETER Protocol Specifies the protocol to use. The acceptable values for this parametDer are: DCOM, Default, or Wsman. .PARAMETER Class Specifies the name of the CIM class for which to retrieve the CIM instances. You can use tab completion to browse the list of classes, because PowerShell gets a list of classes from the local WMI server to provide a list of class names. .PARAMETER Properties Specifies a set of instance properties to retrieve. Use this parameter when you need to reduce the size of the object returned, either in memory or over the network. The object returned also contains the key properties even if you have not listed them using the Property parameter. Other properties of the class are present but they are not populated. .PARAMETER NameSpace Specifies the namespace for the CIM operation. The default namespace is root\cimv2. You can use tab completion to browse the list of namespaces, because PowerShell gets a list of namespaces from the local WMI server to provide a list of namespaces. .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .EXAMPLE Get-CimData -Class 'win32_bios' -ComputerName AD1,EVOWIN .EXAMPLE Get-CimData -Class 'win32_bios' .EXAMPLE Get-CimClass to get all classes .NOTES General notes #> [CmdletBinding()] param( [parameter(Mandatory)][string] $Class, [string] $NameSpace = 'root\cimv2', [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [pscredential] $Credential, [string[]] $Properties = '*' ) $ExcludeProperties = 'CimClass', 'CimInstanceProperties', 'CimSystemProperties', 'SystemCreationClassName', 'CreationClassName' [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $CimObject = @( [string[]] $PropertiesOnly = $Properties | Where-Object { $_ -ne 'PSComputerName' } $Computers = $ComputersSplit[1] if ($Computers.Count -gt 0) { if ($Protocol -eq 'Default' -and $null -eq $Credential) { Get-CimInstance -ClassName $Class -ComputerName $Computers -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } else { $Option = New-CimSessionOption -Protocol $Protocol $newCimSessionSplat = @{ ComputerName = $Computers SessionOption = $Option ErrorAction = 'SilentlyContinue' } if ($Credential) { $newCimSessionSplat['Credential'] = $Credential } $Session = New-CimSession @newCimSessionSplat -Verbose:$false if ($Session) { Try { $Info = Get-CimInstance -ClassName $Class -CimSession $Session -ErrorAction Stop -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsToProcess | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties } catch { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } try { $null = Remove-CimSession -CimSession $Session -ErrorAction SilentlyContinue } catch { Write-Warning -Message "Get-CimData - Failed to remove CimSession $($Session). Failed with errror: $($E.Exception.Message)" } $Info } else { Write-Warning -Message "Get-CimData - Failed to create CimSession for $($Computers). Problem with credentials?" } } foreach ($E in $ErrorsToProcess) { Write-Warning -Message "Get-CimData - No data for computer $($E.OriginInfo.PSComputerName). Failed with errror: $($E.Exception.Message)" } } else { $Computers = $ComputersSplit[0] if ($Computers.Count -gt 0) { $Info = Get-CimInstance -ClassName $Class -ErrorAction SilentlyContinue -Property $PropertiesOnly -Namespace $NameSpace -Verbose:$false -ErrorVariable ErrorsLocal | Select-Object -Property $Properties -ExcludeProperty $ExcludeProperties $Info | Add-Member -Name 'PSComputerName' -Value $Computers -MemberType NoteProperty -Force $Info } foreach ($E in $ErrorsLocal) { Write-Warning -Message "Get-CimData - No data for computer $($Env:COMPUTERNAME). Failed with errror: $($E.Exception.Message)" } } ) $CimObject } function Get-Computer { <# .SYNOPSIS Retrieves various information about a computer or server based on specified types. .DESCRIPTION This function retrieves information about a computer or server based on the specified types. It can gather details about applications, BIOS, CPU, RAM, Disk, Logical Disk, Network, Network Firewall, Operating System, Services, System, Startup, Time, and Windows Updates. .PARAMETER ComputerName Specifies the name of the computer or server to retrieve information from. Defaults to the local computer. .PARAMETER Type Specifies the types of information to retrieve. Valid values include 'Application', 'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical', 'Network', 'NetworkFirewall', 'OperatingSystem', 'Services', 'System', 'Startup', 'Time', and 'WindowsUpdates'. If not specified, retrieves all available types. .PARAMETER AsHashtable Indicates whether to return the output as a hashtable. .EXAMPLE Get-Computer -ComputerName "Server01" -Type "CPU", "RAM" Retrieves CPU and RAM information from a remote server named Server01. .EXAMPLE Get-Computer -ComputerName "Workstation01" -Type "Application" -AsHashtable Retrieves application information from a workstation named Workstation01 and returns the output as a hashtable. #> [cmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Application', 'BIOS', 'CPU', 'RAM', 'Disk', 'DiskLogical', 'Network', 'NetworkFirewall', 'OperatingSystem', 'Services', 'System', 'Startup', 'Time', 'WindowsUpdates' )][string[]] $Type, [switch] $AsHashtable ) Begin { } Process { foreach ($Computer in $ComputerName) { $OutputObject = [ordered] @{} if ($Type -contains 'Application' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Application for $Computer" $Application = Get-ComputerApplication -ComputerName $Computer $OutputObject['Application'] = $Application } if ($Type -contains 'BIOS' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing BIOS for $Computer" $BIOS = Get-ComputerBios -ComputerName $Computer $OutputObject['BIOS'] = $BIOS } if ($Type -contains 'CPU' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing CPU for $Computer" $CPU = Get-ComputerCPU -ComputerName $Computer $OutputObject['CPU'] = $CPU } if ($Type -contains 'RAM' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing RAM for $Computer" $RAM = Get-ComputerRAM -ComputerName $Computer $OutputObject['RAM'] = $RAM } if ($Type -contains 'Disk' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Disk for $Computer" $Disk = Get-ComputerDisk -ComputerName $Computer $OutputObject['Disk'] = $Disk } if ($Type -contains 'DiskLogical' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing DiskLogical for $Computer" $DiskLogical = Get-ComputerDiskLogical -ComputerName $Computer $OutputObject['DiskLogical'] = $DiskLogical } if ($Type -contains 'OperatingSystem' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing OperatingSystem for $Computer" $OperatingSystem = Get-ComputerOperatingSystem -ComputerName $Computer $OutputObject['OperatingSystem'] = $OperatingSystem } if ($Type -contains 'Network' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Network for $Computer" $Network = Get-ComputerNetwork -ComputerName $Computer $OutputObject['Network'] = $Network } if ($Type -contains 'NetworkFirewall' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing NetworkFirewall for $Computer" $NetworkFirewall = Get-ComputerNetwork -ComputerName $Computer -NetworkFirewallOnly $OutputObject['NetworkFirewall'] = $NetworkFirewall } if ($Type -contains 'RDP' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing RDP for $Computer" $RDP = Get-ComputerRDP -ComputerName $Computer $OutputObject['RDP'] = $RDP } if ($Type -contains 'Services' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Services for $Computer" $Services = Get-ComputerService -ComputerName $Computer $OutputObject['Services'] = $Services } if ($Type -contains 'System' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing System for $Computer" $System = Get-ComputerSystem -ComputerName $Computer $OutputObject['System'] = $System } if ($Type -contains 'Startup' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Startup for $Computer" $Startup = Get-ComputerStartup -ComputerName $Computer $OutputObject['Startup'] = $Startup } if ($Type -contains 'Time' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Time for $Computer" $Time = Get-ComputerTime -TimeTarget $Computer $OutputObject['Time'] = $Time } if ($Type -contains 'Tasks' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing Tasks for $Computer" $Tasks = Get-ComputerTask -ComputerName $Computer $OutputObject['Tasks'] = $Tasks } if ($Type -contains 'WindowsUpdates' -or $null -eq $Type) { Write-Verbose "Get-Computer - Processing WindowsUpdates for $Computer" $WindowsUpdates = Get-ComputerWindowsUpdates -ComputerName $Computer $OutputObject['WindowsUpdates'] = $WindowsUpdates } if ($AsHashtable) { $OutputObject } else { [PSCustomObject] $OutputObject } } } } function Get-ComputerApplication { <# .SYNOPSIS Get software installed on computer or server .DESCRIPTION Get software installed on computer or server .PARAMETER ComputerName Specifies computer on which you want to run the operation. .EXAMPLE Get-ComputerApplications -Verbose | Format-Table .EXAMPLE Get-ComputerApplications -Verbose -ComputerName AD1, AD2 | Format-Table .NOTES General notes #> [alias('Get-ComputerApplications')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) $ScriptBlock = { $objapp1 = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* $objapp2 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* $app1 = $objapp1 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x64' }; Label = 'WindowsType' } $app2 = $objapp2 | Select-Object Displayname, Displayversion , Publisher, Installdate, @{Expression = { 'x86' }; Label = 'WindowsType' } | Where-Object { -NOT (([string]$_.displayname).contains('Security Update for Microsoft') -or ([string]$_.displayname).contains('Update for Microsoft')) } $app = $app1 + $app2 $app | Where-Object { $null -ne $_.Displayname } } foreach ($Computer in $ComputerName) { try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer } if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $Parameters = @{ ScriptBlock = $ScriptBlock } } else { $Parameters = @{ ComputerName = $Computer ScriptBlock = $ScriptBlock } } try { $Data = Invoke-Command @Parameters } catch { Write-Warning "Get-ComputerApplication - No data for computer $Computer" continue } foreach ($Information in $Data) { if ($Information.Installdate) { try { $InstallDate = [datetime]::ParseExact($Information.Installdate, 'yyyyMMdd', $null) } catch { Write-Verbose "Get-ComputerApplication - InstallDate $($Information.Installdate) couldn't be converted." $InstallDate = $null } } else { $InstallDate = $null } [PSCustomObject] @{ DisplayName = $Information.DisplayName Version = $Information.DisplayVersion Publisher = $Information.Publisher Installdate = $InstallDate Type = $Information.WindowsType ComputerName = $Computer } } } } function Get-ComputerBios { <# .SYNOPSIS Retrieves BIOS information from a remote or local computer. .DESCRIPTION This function retrieves BIOS information from a specified computer using CIM/WMI. .PARAMETER ComputerName Specifies the name of the computer to retrieve BIOS information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for communication. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'. .PARAMETER All Switch parameter to retrieve all available BIOS properties. .EXAMPLE Get-ComputerBios -ComputerName "RemoteComputer" -Protocol Wsman Retrieves BIOS information from a remote computer using the Wsman protocol. .EXAMPLE Get-ComputerBios -All Retrieves all available BIOS information from the local computer. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_bios' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Status', 'Version', 'PrimaryBIOS', 'Manufacturer', 'ReleaseDate', 'SerialNumber', 'SMBIOSBIOSVersion', 'SMBIOSMajorVersion', 'SMBIOSMinorVersion', 'SystemBiosMajorVersion', 'SystemBiosMinorVersion' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Status = $Data.Status Version = $Data.Version VersionBIOS = -join ($Data.SMBIOSMajorVersion, ".", $Data.SMBIOSMinorVersion, ".", $Data.SystemBiosMajorVersion, ".", $Data.SystemBiosMinorVersion) PrimaryBIOS = $Data.PrimaryBIOS Manufacturer = $Data.Manufacturer ReleaseDate = $Data.ReleaseDate } } } } } function Get-ComputerCPU { <# .SYNOPSIS Retrieves CPU information from specified computers. .DESCRIPTION This function retrieves CPU information from the specified computers. It provides details such as Name, DeviceID, Caption, SystemName, CurrentClockSpeed, MaxClockSpeed, ProcessorID, ThreadCount, Architecture, Status, LoadPercentage, L3CacheSize, Manufacturer, NumberOfCores, NumberOfEnabledCore, and NumberOfLogicalProcessors. .PARAMETER ComputerName Specifies the names of the computers for which to retrieve CPU information. .PARAMETER Protocol Specifies the protocol to use for retrieving CPU information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER All Indicates whether to retrieve all available CPU information. .EXAMPLE Get-ComputerCPU -ComputerName Server01, Server02 -Protocol Wsman -All Retrieves all available CPU information from remote computers Server01 and Server02 using Wsman protocol. .EXAMPLE Get-ComputerCPU -ComputerName "Workstation01" -Protocol Default Retrieves CPU information from a single remote computer named Workstation01 using the default protocol. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_processor' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'PSComputerName', 'Name', 'DeviceID', 'Caption', 'SystemName', 'CurrentClockSpeed', 'MaxClockSpeed', 'ProcessorID', 'ThreadCount', 'Architecture', 'Status', 'LoadPercentage', 'L3CacheSize', 'Manufacturer', 'VirtualizationFirmwareEnabled', 'NumberOfCores', 'NumberOfEnabledCore', 'NumberOfLogicalProcessors' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Name = $Data.Name DeviceID = $Data.DeviceID Caption = $Data.Caption CurrentClockSpeed = $Data.CurrentClockSpeed MaxClockSpeed = $Data.MaxClockSpeed ProcessorID = $Data.ProcessorID ThreadCount = $Data.ThreadCount Architecture = $Data.Architecture Status = $Data.Status LoadPercentage = $Data.LoadPercentage Manufacturer = $Data.Manufacturer NumberOfCores = $Data.NumberOfCores NumberOfEnabledCore = $Data.NumberOfEnabledCore NumberOfLogicalProcessors = $Data.NumberOfLogicalProcessors } } } } } function Get-ComputerCulture { <# .SYNOPSIS Retrieves culture information from a specified computer. .DESCRIPTION This function retrieves culture information from the specified computer. It provides details such as KeyboardLayoutId, DisplayName, and Windows Language. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve culture information. Defaults to the local computer. .EXAMPLE Get-ComputerCulture Retrieves culture information from the local computer. .EXAMPLE Get-ComputerCulture -ComputerName "Server01" Retrieves culture information from a remote computer named Server01. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME ) $ScriptBlock = { Get-Culture | Select-Object KeyboardLayoutId, DisplayName, @{Expression = { $_.ThreeLetterWindowsLanguageName }; Label = "Windows Language" } } if ($ComputerName -eq $Env:COMPUTERNAME) { $Data8 = Invoke-Command -ScriptBlock $ScriptBlock } else { $Data8 = Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock } return $Data8 } function Get-ComputerDevice { <# .SYNOPSIS Retrieves information about computer devices. .DESCRIPTION This function retrieves information about computer devices using WMI. .PARAMETER ComputerName Specifies the name of the computer to query. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for the query. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'. .PARAMETER All Retrieves all properties of the computer devices. .PARAMETER Extended Retrieves extended properties of the computer devices. .EXAMPLE Get-ComputerDevice -ComputerName "Computer01" -Protocol "Wsman" -All Retrieves all properties of computer devices from a remote computer using Wsman protocol. .EXAMPLE Get-ComputerDevice -ComputerName "Computer02" -Extended Retrieves extended properties of computer devices from a remote computer. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All, [switch] $Extended ) [string] $Class = 'win32_pnpentity' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = @( 'PNPClass' 'Name' 'Status' 'ConfigManagerErrorCode' 'DeviceID' 'ErrorCleared' 'ErrorDescription' 'LastErrorCode' 'StatusInfo' 'ClassGuid' 'CompatibleID' 'HardwareID' 'Manufacturer' 'PSComputerName' ) } $ConfigManagerErrorCode = @{ '0' = "This device is working properly." '1' = 'This device is not configured correctly.' '2' = 'Windows cannot load the driver for this device.' '3' = "The driver for this device might be corrupted, or your system may be running low on memory or other resources." '4' = "This device is not working properly. One of its drivers or your registry might be corrupted." '5' = "The driver for this device needs a resource that Windows cannot manage." '6' = "The boot configuration for this device conflicts with other devices." '7' = "Cannot filter." '8' = "The driver loader for the device is missing." '9' = "This device is not working properly because the controlling firmware is reporting the resources for the device incorrectly." '10' = "This device cannot start." '11' = "This device failed." '12' = "This device cannot find enough free resources that it can use." '13' = "Windows cannot verify this device's resources." '14' = "This device cannot work properly until you restart your computer." '15' = "This device is not working properly because there is probably a re-enumeration problem." '16' = "Windows cannot identify all the resources this device uses." '17' = "This device is asking for an unknown resource type." '18' = "Reinstall the drivers for this device." '19' = "Failure using the VxD loader." '20' = "Your registry might be corrupted." '21' = "System failure: Try changing the driver for this device. If that does not work, see your hardware documentation. Windows is removing this device." '22' = "This device is disabled." '23' = "System failure: Try changing the driver for this device. If that doesn't work, see your hardware documentation." '24' = "This device is not present, is not working properly, or does not have all its drivers installed." '25' = "Windows is still setting up this device." '26' = "Windows is still setting up this device." '27' = "This device does not have valid log configuration." '28' = "The drivers for this device are not installed." '29' = "This device is disabled because the firmware of the device did not give it the required resources." '30' = "This device is using an Interrupt Request (IRQ) resource that another device is using." '31' = "This device is not working properly because Windows cannot load the drivers required for this device." } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { $Device = [ordered]@{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } 'DeviceClass' = $Data.PNPClass 'Name' = $Data.Name 'Status' = $Data.Status 'ErrorCode' = $ConfigManagerErrorCode["$($Data.ConfigManagerErrorCode)"] 'DeviceID' = $Data.DeviceID } if ($Extended) { $DeviceUpgrade = [ordered]@{ 'ErrorCleared' = $Data.ErrorCleared 'ErrorDescription' = $Data.ErrorDescription 'LastErrorCode' = $Data.LastErrorCode 'StatusInfo' = $Data.StatusInfo 'ClassGuid' = $Data.ClassGuid 'CompatibleID' = $Data.CompatibleID 'HardwareID' = $Data.HardwareID 'Manufacturer' = if ($Data.Manufacturer) { $Data.Manufacturer.Replace('(', '').Replace(')', '') } else { } } [PSCustomObject] ($Device + $DeviceUpgrade) } else { [PSCustomObject] $Device } } } } } function Get-ComputerDisk { <# .SYNOPSIS Retrieves disk information from remote computers. .DESCRIPTION This function retrieves disk information from remote computers specified by the ComputerName parameter. It provides details such as Index, Model, Caption, SerialNumber, Description, MediaType, FirmwareRevision, Partitions, SizeGB, and PNPDeviceID. .PARAMETER ComputerName Specifies the names of the remote computers from which to retrieve disk information. .PARAMETER Protocol Specifies the protocol to be used for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER All Indicates whether to retrieve all available disk information. .EXAMPLE Get-ComputerDisk -ComputerName AD1, AD2, EVO1, AD2019 | Format-Table -AutoSize * Output: WARNING: Get-ComputerSystem - No data for computer AD2019. Most likely an error on receiving side. ComputerName Index Model Caption SerialNumber Description MediaType FirmwareRevision Partitions SizeGB PNPDeviceID ------------ ----- ----- ------- ------------ ----------- --------- ---------------- ---------- ------ ----------- AD1 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000 AD2 0 Microsoft Virtual Disk Microsoft Virtual Disk Disk drive Fixed hard disk media 1.0 3 127 SCSI\DISK&VEN_MSFT&PROD_VIRTUAL_DISK\000000 EVO1 0 WDC WD30EFRX-68AX9N0 WDC WD30EFRX-68AX9N0 WD-WMC1T2351095 Disk drive Fixed hard disk media 80.00A80 1 2795 SCSI\DISK&VEN_WDC&PROD_WD30EFRX-68AX9N0\4&191557A4&0&000000 EVO1 2 Samsung SSD 950 PRO 512GB Samsung SSD 950 PRO 512GB 0025_3857_61B0_0EF2. Disk drive Fixed hard disk media 2B0Q 3 477 SCSI\DISK&VEN_NVME&PROD_SAMSUNG_SSD_950\5&35365596&0&000000 EVO1 1 Samsung SSD 860 EVO 500GB Samsung SSD 860 EVO 500GB S3Z2NB0K176976A Disk drive Fixed hard disk media RVT01B6Q 1 466 SCSI\DISK&VEN_SAMSUNG&PROD_SSD\4&191557A4&0&000100 .NOTES This function uses the Get-CimData cmdlet to retrieve disk information from remote computers. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_diskdrive' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Index', 'Model', 'Caption', 'SerialNumber', 'Description', 'MediaType', 'FirmwareRevision', 'Partitions', 'Size', 'PNPDeviceID', 'PSComputerName' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Index = $Data.Index Model = $Data.Model Caption = $Data.Caption SerialNumber = if ($Data.SerialNumber) { $Data.SerialNumber.Trim() } else { '' } Description = $Data.Description MediaType = $Data.MediaType FirmwareRevision = $Data.FirmwareRevision Partitions = $Data.Partitions SizeGB = $Data.Size / 1Gb -as [int] PNPDeviceID = $Data.PNPDeviceID } } } } } function Get-ComputerDiskLogical { <# .SYNOPSIS Retrieves logical disk information for specified computers. .DESCRIPTION This function retrieves logical disk information for the specified computers. It provides details such as DeviceID, DriveType, ProviderName, FreeSpace, UsedSpace, TotalSpace, FreePercent, UsedPercent, and VolumeName. .PARAMETER ComputerName Specifies the names of the computers for which to retrieve disk information. .PARAMETER Protocol Specifies the protocol to use for retrieving disk information. Valid values are 'Default', 'Dcom', and 'Wsman'. .PARAMETER RoundingPlace Specifies the number of decimal places to round the disk space values to. .PARAMETER OnlyLocalDisk Indicates that only local disks should be included in the output. .PARAMETER All Indicates that information for all disks should be retrieved. .EXAMPLE Get-ComputerDiskLogical -ComputerName AD1, AD2, EVOWIN -OnlyLocalDisk | ft -AutoSize Output: ComputerName DeviceID DriveType ProviderName FreeSpace UsedSpace TotalSpace FreePercent UsedPercent VolumeName ------------ -------- --------- ------------ --------- --------- ---------- ----------- ----------- ---------- AD2 C: Local Disk 96,96 29,49 126,45 76,68 23,32 AD1 C: Local Disk 103,17 23,28 126,45 81,59 18,41 EVOWIN C: Local Disk 133,31 343,03 476,34 27,99 72,01 EVOWIN D: Local Disk 2433 361,4 2794,39 87,07 12,93 Media EVOWIN E: Local Disk 66,05 399,7 465,75 14,18 85,82 Testing Environment .NOTES Additional notes about the function. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [string][ValidateSet('GB', 'TB', 'MB')] $Size = 'GB', [int] $RoundingPlace = 2, [int] $RoundingPlacePercent = 2, [switch] $OnlyLocalDisk, [switch] $All ) if (-Not ('Pinvoke.Win32Utils' -as [type])) { Add-Type -TypeDefinition @' using System.Runtime.InteropServices; using System.Text; namespace pinvoke { public static class Win32Utils { [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern uint QueryDosDevice(string lpDeviceName, StringBuilder lpTargetPath, int ucchMax); } } '@ } [string] $Class = 'win32_logicalDisk' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'DeviceID', 'DriveType', 'ProviderName', 'FreeSpace', 'Size', 'VolumeName', 'PSComputerName' } $DriveType = @{ '0' = 'Unknown' '1' = 'No Root Directory' '2' = 'Removable Disk' '3' = 'Local Disk' '4' = 'Network Drive' '5' = 'Compact Disc' '6' = 'RAM Disk' } $Divider = "1$Size" $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { $Output = foreach ($Info in $Information) { foreach ($Data in $Info) { if ($Info.ComputerName -eq $Env:COMPUTERNAME) { $DiskPartitionName = [System.Text.StringBuilder]::new(1024) if ($Data.DeviceID) { [void][PInvoke.Win32Utils]::QueryDosDevice(($Data.DeviceID), $DiskPartitionName, $DiskPartitionName.Capacity) } $DiskPartitionNumber = $DiskPartitionName.ToString() } else { $DiskPartitionNumber = '' } [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } DeviceID = $Data.DeviceID DriveType = $DriveType["$($Data.DriveType)"] ProviderName = $Data.ProviderName FreeSpace = [Math]::Round($Data.FreeSpace / $Divider, $RoundingPlace) UsedSpace = [Math]::Round(($Data.Size - $Data.FreeSpace) / $Divider, $RoundingPlace) TotalSpace = [Math]::Round($Data.Size / $Divider, $RoundingPlace) FreePercent = if ($Data.Size -gt 0 ) { [Math]::round(($Data.FreeSpace / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' } UsedPercent = if ($Data.Size -gt 0 ) { [Math]::round((($Data.Size - $Data.FreeSpace) / $Data.Size) * 100, $RoundingPlacePercent) } else { '0' } VolumeName = $Data.VolumeName DiskPartition = $DiskPartitionNumber } } } if ($OnlyLocalDisk) { $Output | Where-Object { $_.DriveType -eq 'Local Disk' } } else { $Output } } } function Get-ComputerFirewall { <# .SYNOPSIS Short description .DESCRIPTION Long description .EXAMPLE $Data = Get-ComputerFirewall $Data | Format-Table -AutoSize .EXAMPLE $Data = Get-ComputerFirewall $Data | Out-HtmlView -ScrollX -Filtering -Title "Firewall Rules" -DataStore JavaScript .NOTES General notes #> [CmdletBinding()] param( [string] $ComputerName ) $CimData = [ordered] @{ CimSession = $ComputerName ErrorAction = 'SilentlyContinue' ErrorVariable = 'ErrorVar' } Remove-EmptyValue -Hashtable $CimData if ($ComputerName) { $ComputerNameDisplay = $ComputerName } else { $ComputerNameDisplay = 'Localhost' } Write-Verbose -Message "Get-ComputerFirewall - Getting firewall rules ($ComputerNameDisplay)" $Allrules = Get-NetFirewallRule @CimData if ($ErrorVar) { Write-Warning -Message "Get-ComputerFirewall - Error getting firewall rules. Try running as admin? $($ErrorVar)" } Write-Verbose -Message "Get-ComputerFirewall - Getting firewall filters ($ComputerNameDisplay)" $Allports = Get-NetFirewallPortFilter @CimData if ($ErrorVar) { Write-Warning -Message "Get-ComputerFirewall - Error getting firewall ports. Try running as admin? $($ErrorVar)" } Write-Verbose -Message "Get-ComputerFirewall - Getting firewall filters ($ComputerNameDisplay)" $AllFilters = Get-NetFirewallApplicationFilter @CimData | Select-Object Program, AppPath, InstanceID, Description, Package if ($ErrorVar) { Write-Warning -Message "Get-ComputerFirewall - Error getting firewall filters. Try running as admin? $($ErrorVar)" } Write-Verbose -Message "Get-ComputerFirewall - Matching rules, ports and filters" $FiltersCache = @{} $RulesCache = @{} $PortCache = @{} foreach ($Rule in $Allrules) { $RulesCache[$Rule.Name] = $Rule } foreach ($Port in $Allports) { $PortCache[$Port.InstanceID] = $Port } foreach ($Filter in $AllFilters) { $FiltersCache[$Filter.InstanceID] = $Filter } foreach ($Rule in $RulesCache.Values) { [PSCustomObject]@{ Name = $Rule.Name ID = $Rule.ID DisplayName = $Rule.DisplayName Group = $Rule.Group Enabled = $Rule.Enabled Profile = $Rule.Profile Platform = $Rule.Platform Direction = $Rule.Direction Action = $Rule.Action EdgeTraversalPolicy = $Rule.EdgeTraversalPolicy LSM = $Rule.LSM PrimaryStatus = $Rule.PrimaryStatus Status = $Rule.Status EnforcementStatus = $Rule.EnforcementStatus PolicyStoreSourceType = $Rule.PolicyStoreSourceType Caption = $Rule.Caption Description = $Rule.Description ElementName = $Rule.ElementName InstanceID = $Rule.InstanceID CommonName = $Rule.CommonName PolicyKeywords = $Rule.PolicyKeywords PolicyDecisionStrategy = $Rule.PolicyDecisionStrategy PolicyRoles = $Rule.PolicyRoles ConditionListType = $Rule.ConditionListType CreationClassName = $Rule.CreationClassName ExecutionStrategy = $Rule.ExecutionStrategy Mandatory = $Rule.Mandatory PolicyRuleName = $Rule.PolicyRuleName Priority = $Rule.Priority RuleUsage = $Rule.RuleUsage SequencedActions = $Rule.SequencedActions SystemCreationClassName = $Rule.SystemCreationClassName SystemName = $Rule.SystemName DisplayGroup = $Rule.DisplayGroup LocalOnlyMapping = $Rule.LocalOnlyMapping LooseSourceMapping = $Rule.LooseSourceMapping Owner = $Rule.Owner PackageFamilyName = $Rule.PackageFamilyName Platforms = $Rule.Platforms PolicyAppId = $Rule.PolicyAppId PolicyStoreSource = $Rule.PolicyStoreSource Profiles = $Rule.Profiles RemoteDynamicKeywordAddresses = $Rule.RemoteDynamicKeywordAddresses RuleGroup = $Rule.RuleGroup StatusCode = $Rule.StatusCode Program = $FiltersCache[$Rule.Name].Program AppPath = $FiltersCache[$Rule.Name].AppPath Protocol = $PortCache[$Rule.Name].Protocol LocalPort = $PortCache[$Rule.Name].LocalPort RemotePort = $PortCache[$Rule.Name].RemotePort IcmpType = $PortCache[$Rule.Name].IcmpType DynamicTarget = $PortCache[$Rule.Name].DynamicTarget } } } function Get-ComputerInstalledUpdates { <# .SYNOPSIS This function retrieves the history of updates installed on a computer or multiple computers. .DESCRIPTION The `Get-ComputerInstalledUpdates` function uses the Windows Update Agent API to query the history of updates installed on a computer. It returns a list of updates with details such as the KB number, installation date, update type, version, and more. .PARAMETER ComputerName An array of computer names. The default value is the name of the current computer. .PARAMETER IncludeType An array of update types to include in the results. Valid values are 'Antivirus', 'CumulativeUpdate', 'WindowsUpdate', 'ServicingStackUpdate', and 'Other'. .PARAMETER ExcludeType An array of update types to exclude from the results. Valid values are the same as for `IncludeType`. .PARAMETER SearchKB A string to search for in the KB numbers of the updates. .PARAMETER Credential A PSCredential object to use when connecting to the computers. .EXAMPLE # Get all updates installed on the current computer Get-ComputerInstalledUpdates .EXAMPLE # Get all 'Windows Update' type updates installed on the computer 'Computer1' Get-ComputerInstalledUpdates -ComputerName 'Computer1' -IncludeType 'WindowsUpdate' .EXAMPLE # Get all updates except 'Antivirus' type updates installed on the computers 'Computer1' and 'Computer2' Get-ComputerInstalledUpdates -ComputerName 'Computer1', 'Computer2' -ExcludeType 'Antivirus' .EXAMPLE # Search for a specific KB number on the current computer Get-ComputerInstalledUpdates -SearchKB 'KB123456' .NOTES This function uses the COM interface of the Windows Update Agent API, which requires administrative privileges. If you run this function without administrative privileges, it may not return all updates. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet( 'Antivirus', 'CumulativeUpdate', 'WindowsUpdate', 'ServicingStackUpdate', 'Other' )][string[]] $IncludeType, [ValidateSet( 'Antivirus', 'CumulativeUpdate', 'WindowsUpdate', 'ServicingStackUpdate', 'Other' )][string[]] $ExcludeType, [string] $SearchKB, [pscredential] $Credential ) $Properties = @( 'ComputerName', 'InstalledOn', 'KB', 'Categories', 'Type', 'Version', 'Title', 'ClientApplicationID', 'InstalledFrom', 'Description', 'Operation', 'Status', 'SupportUrl', 'UpdateID', 'RevisionNumber', 'UnmappedResultCode', 'HResult', 'ServiceID', 'UninstallationSteps', 'UninstallationNotes' ) $ScriptBlock = { $Session = New-Object -ComObject Microsoft.Update.Session $Searcher = $Session.CreateUpdateSearcher() $historyCount = $Searcher.GetTotalHistoryCount() $kbRegex = '(KB\d+)' $versionRegex = 'Version (\d+\.\d+\.\d+\.\d+)' $QueryHistory = $Searcher.QueryHistory(0, $historyCount) $QueryHistory | Where-Object date -GT (Get-Date "1/1/2000") | ForEach-Object { $KbMatch = Select-String $kbRegex -InputObject $_.Title $Kb = if ($kbMatch) { $kbMatch.matches.groups[0].value } else { $null } $VersionMatch = Select-String $VersionRegex -InputObject $_.Title $Version = if ($VersionMatch) { $VersionMatch.matches.groups[1].value } else { $null } $Categories = $_.Categories | ForEach-Object { $_.Name } if ($_.Title -like "*Security Intelligence Update*" -or $_.Title -like "*Antivirus*" -or $_.Title -like "*Malicious Software*") { $Type = "Antivirus" } elseif ($_.Title -like "*Cumulative Update*") { $Type = "CumulativeUpdate" } elseif ($_.Title -like "*Update for Windows*" -or $_.Title -like "*Security Update*" -or $_.Title -like "*Update for Microsoft*" -or $_.Title -like "*Update for Internet Explorer*") { $Type = "WindowsUpdate" } elseif ($_.Title -like "*Servicing Stack Update*") { $Type = "ServicingStackUpdate" } else { $Type = "Other" } [PSCustomObject]@{ "ComputerName" = $Env:COMPUTERNAME "InstalledOn" = $_.Date "KB" = $Kb "Categories" = $Categories "Type" = $Type "Version" = $Version "ClientApplicationID" = $_.ClientApplicationID "InstalledFrom" = switch ($_.ServerSelection) { 0 { "Default" }; 1 { "Managed Server" }; 2 { "Windows Update" }; 3 { "Others" } } "Title" = $_.Title "Description" = $_.Description "Operation" = switch ($_.operation) { 1 { "Installation" }; 2 { "Uninstallation" }; 3 { "Other" } } "Status" = switch ($_.resultcode) { 1 { "In Progress" }; 2 { "Succeeded" }; 3 { "Succeeded With Errors" }; 4 { "Failed" }; 5 { "Aborted" } } "SupportUrl" = $_.SupportUrl "UpdateID" = $_.UpdateIdentity.UpdateID "RevisionNumber" = $_.UpdateIdentity.RevisionNumber "UnmappedResultCode" = $_.UnmappedResultCode "HResult" = $_.HResult "ServiceID" = $_.ServiceID "UninstallationSteps" = $_.UninstallationSteps.Name "UninstallationNotes" = $_.UninstallationNotes } } } [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $Computers = $ComputersSplit[1] $Data = if ($Computers.Count -gt 0) { foreach ($Computer in $Computers) { try { $invokeCommandSplat = @{ ComputerName = $Computer ScriptBlock = $ScriptBlock ErrorAction = 'Stop' } if ($Credential) { $invokeCommandSplat.Credential = $Credential } Invoke-Command @invokeCommandSplat | Select-Object -Property $Properties } catch { Write-Warning -Message "Get-ComputerInstalledUpdates - No data for computer $Computer. Failed with error: $($_.Exception.Message)" } } } else { try { $invokeCommandSplat = @{ ScriptBlock = $ScriptBlock ErrorAction = 'Stop' } if ($Credential) { $invokeCommandSplat.Credential = $Credential } Invoke-Command @invokeCommandSplat } catch { Write-Warning -Message "Get-ComputerInstalledUpdates - No data for computer $($Env:COMPUTERNAME). Failed with error: $($_.Exception.Message)" } } if ($SearchKB) { foreach ($D in $Data) { if ($D.KB -like "*$SearchKB*") { $D } } } else { foreach ($Update in $Data) { if ($IncludeType -and $IncludeType -notcontains $Update.Type) { continue } if ($ExcludeType -and $ExcludeType -contains $Update.Type) { continue } $Update } } } function Get-ComputerMemory { <# .SYNOPSIS Retrieves memory information from specified computers. .DESCRIPTION This function retrieves memory information from specified computers, including details about physical memory usage, virtual memory usage, and memory percentages. .PARAMETER ComputerName Specifies the name of the computer(s) to retrieve memory information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for retrieving memory information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'. .PARAMETER All Switch parameter to retrieve all available memory properties. .EXAMPLE Get-ComputerMemory -ComputerName "Server01" Retrieves memory information from a remote computer named Server01. .EXAMPLE Get-ComputerMemory -ComputerName "WorkstationA", "WorkstationB" -Protocol Wsman -All Retrieves all available memory properties from multiple remote computers using the Wsman protocol. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_operatingsystem' if ($All) { [string] $Properties = '*' } else { [string] $Properties = "*" } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Data in $Information) { $FreeMB = [math]::Round($Data.FreePhysicalMemory / 1024, 2) $TotalMB = [math]::Round($Data.TotalVisibleMemorySize / 1024, 2) $UsedMB = $TotalMB - $FreeMB $UsedPercent = [math]::Round(($UsedMB / $TotalMB) * 100, 2) $FreeVirtualMB = [math]::Round($Data.FreeVirtualMemory / 1024, 2) $TotalVirtualMB = [math]::Round($Data.TotalVirtualMemorySize / 1024, 2) $UsedVirtualMB = $TotalVirtualMB - $FreeVirtualMB $UsedVirtualPercent = [math]::Round(($UsedVirtualMB / $TotalVirtualMB) * 100, 2) [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } MemoryUsed = $UsedMB MemoryFree = $FreeMB MemoryTotal = $TotalMB MemoryUsedPercentage = $UsedPercent VirtualMemoryUsed = $UsedVirtualMB VirtualMemoryUsedPercentage = $UsedVirtualPercent VirtualMemoryFree = $FreeVirtualMB VirtualMemoryTotal = $TotalVirtualMB } } } } function Get-ComputerMissingDrivers { <# .SYNOPSIS Retrieves information about missing drivers on a specified computer. .DESCRIPTION This function retrieves information about missing drivers on a specified computer by querying the Win32_PNPEntity WMI class. .PARAMETER ComputerName Specifies the name of the computer to query. Defaults to the local computer. .EXAMPLE Get-ComputerMissingDrivers -ComputerName "Computer01" Retrieves information about missing drivers on a computer named "Computer01". .EXAMPLE Get-ComputerMissingDrivers Retrieves information about missing drivers on the local computer. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME ) $Data = Get-WmiObject Win32_PNPEntity -ComputerName $ComputerName | Where-Object { $_.Configmanagererrorcode -ne 0 } | Select-Object Caption, ConfigmanagererrorCode, Description, DeviceId, HardwareId, PNPDeviceID return $Data } function Get-ComputerNetFramework { <# .SYNOPSIS Get the installed .NET Framework version on a computer .DESCRIPTION Get the installed .NET Framework version on a computer .PARAMETER ComputerName The name of the computer to check the .NET Framework version on .EXAMPLE $DCs = Get-ADDomainController -Filter * -Server 'ad.evotec.xyz' Get-ComputerNetFramework -ComputerName $Dcs.HostName | Format-Table * .NOTES General notes #> [CmdletBinding()] param( [string[]] $ComputerName = $env:COMPUTERNAME ) foreach ($Computer in $ComputerName) { $Output1 = Get-PSRegistry -ComputerName $Computer -RegistryPath 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Client' if ($Output1.PSError -eq $false) { [PSCustomObject] @{ ComputerName = $Computer NetFrameworkVersion = $Output1.Version NetFrameworkRelease = $Output1.Release Message = "" } } else { [PSCustomObject] @{ ComputerName = $Computer NetFrameworkVersion = 'Not Installed' NetFrameworkRelease = 'Not Installed' Message = $Output1.PSErrorMessage } } } } function Get-ComputerNetwork { <# .SYNOPSIS Retrieves network information for specified computers. .DESCRIPTION This function retrieves network information for the specified computers, including details about network cards, firewall profiles, and connectivity status. .PARAMETER ComputerName Specifies the name of the computer(s) for which to retrieve network information. .PARAMETER NetworkFirewallOnly Indicates whether to retrieve only firewall information for the specified computers. .PARAMETER NetworkFirewallSummaryOnly Indicates whether to retrieve a summary of firewall information for the specified computers. .EXAMPLE Get-ComputerNetworkCard -ComputerName AD1, AD2, AD3 Output Name NetworkCardName NetworkCardIndex FirewallProfile FirewallStatus IPv4Connectivity IPv6Connectivity Caption Description ElementName DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogFileName LogMaxSizeKilobytes LogAllowed LogBlo cked ---- --------------- ---------------- --------------- -------------- ---------------- ---------------- ------- ----------- ----------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ----------- ------------------- ---------- ------ ad.evotec.xyz vEthernet (External Switch) 13 DomainAuthenticated True Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network 2 Ethernet 2 2 Private True Internet NoTraffic Block Allow NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network Ethernet 2 Private True LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False ad.evotec.xyz Ethernet 5 3 DomainAuthenticated False Internet NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False Network 2 Ethernet 4 12 Private False LocalNetwork NoTraffic NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured False NotConfigured %systemroot%\system32\LogFiles\Firewall\pfirewall.log 4096 False False .EXAMPLE Get-ComputerNetworkCard -ComputerName EVOWIN -NetworkFirewallOnly PSComputerName Profile Enabled DefaultInboundAction DefaultOutboundAction AllowInboundRules AllowLocalFirewallRules AllowLocalIPsecRules AllowUserApps AllowUserPorts AllowUnicastResponseToMulticast NotifyOnListen EnableStealthModeForIPsec LogMaxSizeKilobytes LogAllowed LogBlocked LogIgnored Caption Description ElementName InstanceID DisabledInterfaceAliases LogFileName Name CimClass -------------- ------- ------- -------------------- --------------------- ----------------- ----------------------- -------------------- ------------- -------------- ------------------------------- -------------- ------------------------- ------------------- ---------- ---------- ---------- ------- ----------- ----------- ---------- ------------------------ ----------- ---- -------- EVOWIN Domain True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Domain {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Domain root/stand... EVOWIN Private True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Private {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Private root/stand... EVOWIN Public True NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured NotConfigured True NotConfigured 4096 False False NotConfigured MSFT|FW|FirewallProfile|Public {NotConfigured} %systemroot%\system32\LogFiles\Firewall\pfirewall.log Public root/stand... .NOTES General notes #> [alias('Get-ComputerNetworkCard')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [switch] $NetworkFirewallOnly, [switch] $NetworkFirewallSummaryOnly, [alias('Joiner')][string] $Splitter ) [Array] $CollectionComputers = $ComputerName.Where( { $_ -eq $Env:COMPUTERNAME }, 'Split') $Firewall = @{ } $NetworkFirewall = @( if ($CollectionComputers[0].Count -gt 0) { $Firewall[$Env:COMPUTERNAME] = @{ } $Output = Get-NetFirewallProfile foreach ($_ in $Output) { Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force $_ if ($_.Name -eq 'Domain') { $Firewall[$Env:COMPUTERNAME]['DomainAuthenticated'] = $_ } else { $Firewall[$Env:COMPUTERNAME][$($_.Name)] = $_ } } } if ($CollectionComputers[1].Count -gt 0) { foreach ($_ in $CollectionComputers[1]) { $Firewall[$_] = @{ } } $Output = Get-NetFirewallProfile -CimSession $CollectionComputers[1] foreach ($_ in $Output) { if ($_.Name -eq 'Domain') { $Firewall[$_.PSComputerName]['DomainAuthenticated'] = $_ } else { $Firewall[$_.PSComputerName][$($_.Name)] = $_ } } } ) if ($NetworkFirewallOnly) { return $NetworkFirewall } if ($NetworkFirewallSummaryOnly) { return $Firewall } $NetworkCards = @( if ($CollectionComputers[0].Count -gt 0) { $Output = Get-NetConnectionProfile foreach ($_ in $Output) { Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -Type NoteProperty -Force $_ } } if ($CollectionComputers[1].Count -gt 0) { Get-NetConnectionProfile -CimSession $CollectionComputers[1] } ) foreach ($_ in $NetworkCards) { $NetworkCardsConfiguration = Get-CimData -ComputerName $ComputerName -Class 'Win32_NetworkAdapterConfiguration' $CurrentCard = foreach ($Configuration in $NetworkCardsConfiguration) { if ($_.PSComputerName -eq $Configuration.PSComputerName) { if ($Configuration.InterfaceIndex -eq $_.InterfaceIndex) { $Configuration } } } $NetbiosTCPIP = @{ '0' = 'Default' '1' = 'Enabled' '2' = 'Disabled' } [PSCustomObject] @{ Name = $_.Name NetworkCardName = $_.InterfaceAlias NetworkCardIndex = $_.InterfaceIndex FirewallProfile = $_.NetworkCategory FirewallStatus = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].'Enabled' IPAddress = $CurrentCard.IPAddress IPGateway = $CurrentCard.DefaultIPGateway IPSubnet = $CurrentCard.IPSubnet IPv4Connectivity = $_.IPv4Connectivity IPv6Connectivity = $_.IPv6Connectivity DNSServerSearchOrder = $CurrentCard.DNSServerSearchOrder DNSDomainSuffixSearchOrder = $CurrentCard.DNSDomainSuffixSearchOrder FullDNSRegistrationEnabled = $CurrentCard.FullDNSRegistrationEnabled DHCPEnabled = $CurrentCard.DHCPEnabled DHCPServer = $CurrentCard.DHCPServer DHCPLeaseObtained = $CurrentCard.DHCPLeaseObtained NetBIOSOverTCPIP = $NetBiosTCPIP["$($CurrentCard.TcpipNetbiosOptions)"] Caption = $_.Caption Description = $_.Description ElementName = $_.ElementName DefaultInboundAction = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultInboundAction DefaultOutboundAction = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].DefaultOutboundAction AllowInboundRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowInboundRules AllowLocalFirewallRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalFirewallRules AllowLocalIPsecRules = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowLocalIPsecRules AllowUserApps = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserApps AllowUserPorts = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUserPorts AllowUnicastResponseToMulticast = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].AllowUnicastResponseToMulticast NotifyOnListen = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].NotifyOnListen EnableStealthModeForIPsec = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].EnableStealthModeForIPsec LogFileName = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogFileName LogMaxSizeKilobytes = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogMaxSizeKilobytes LogAllowed = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogAllowed LogBlocked = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogBlocked LogIgnored = $Firewall[$_.PSComputerName]["$($_.NetworkCategory)"].LogIgnored ComputerName = $_.PSComputerName } } } function Get-ComputerOemInformation { <# .SYNOPSIS Retrieves OEM information from a specified computer. .DESCRIPTION This function retrieves OEM information such as Model, Manufacturer, Logo, Support Phone, Support URL, and Support Hours from the specified computer. .PARAMETER ComputerName Specifies the name of the computer from which to retrieve the OEM information. If not specified, the local computer name is used. .EXAMPLE Get-ComputerOemInformation Retrieves OEM information from the local computer. .EXAMPLE Get-ComputerOemInformation -ComputerName "Computer01" Retrieves OEM information from a remote computer named "Computer01". #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME ) $ScriptBlock = { Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OEMInformation | Select-Object Model, Manufacturer, Logo, SupportPhone, SupportURL, SupportHours } if ($ComputerName -eq $Env:COMPUTERNAME) { $Data = Invoke-Command -ScriptBlock $ScriptBlock } else { $Data = Invoke-Command -ComputerName $ComputerName -ScriptBlock $ScriptBlock } return $Data } function Get-ComputerOperatingSystem { <# .SYNOPSIS Retrieves operating system information from remote computers. .DESCRIPTION This function retrieves operating system information from remote computers using CIM/WMI queries. It provides details such as the operating system name, version, manufacturer, architecture, language, product suite, installation date, last boot-up time, and more. .PARAMETER ComputerName Specifies the name of the remote computer(s) to retrieve the operating system information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for the connection (Default, Dcom, or Wsman). Default is 'Default'. .PARAMETER All Switch parameter to retrieve all available properties of the operating system. .EXAMPLE Get-ComputerOperatingSystem -ComputerName "Server01" -Protocol Wsman Retrieves operating system information from a single remote computer named "Server01" using the Wsman protocol. .EXAMPLE Get-ComputerOperatingSystem -ComputerName "Server01", "Server02" -All Retrieves all available operating system properties from multiple remote computers named "Server01" and "Server02". #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_operatingsystem' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Manufacturer', 'InstallDate', 'OSArchitecture', 'Version', 'SerialNumber', 'BootDevice', 'WindowsDirectory', 'CountryCode', 'OSLanguage', 'OSProductSuite', 'PSComputerName', 'LastBootUpTime', 'LocalDateTime' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Data in $Information) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } OperatingSystem = $Data.Caption OperatingSystemVersion = ConvertTo-OperatingSystem -OperatingSystem $Data.Caption -OperatingSystemVersion $Data.Version OperatingSystemBuild = $Data.Version Manufacturer = $Data.Manufacturer OSArchitecture = $Data.OSArchitecture OSLanguage = ConvertFrom-LanguageCode -LanguageCode $Data.OSLanguage OSProductSuite = [Microsoft.PowerShell.Commands.OSProductSuite] $($Data.OSProductSuite) InstallDate = $Data.InstallDate LastBootUpTime = $Data.LastBootUpTime LocalDateTime = $Data.LocalDateTime SerialNumber = $Data.SerialNumber BootDevice = $Data.BootDevice WindowsDirectory = $Data.WindowsDirectory CountryCode = $Data.CountryCode } } } } function Get-ComputerRAM { <# .SYNOPSIS Retrieves information about the RAM of a specified computer. .DESCRIPTION This function retrieves detailed information about the RAM of a specified computer. It provides various properties such as Manufacturer, Model, Capacity, Speed, and more. .PARAMETER ComputerName Specifies the name of the computer to retrieve RAM information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for retrieving RAM information. Valid values are 'Default', 'Dcom', and 'Wsman'. Defaults to 'Default'. .PARAMETER All Indicates whether to retrieve all available properties of the RAM. If specified, all properties will be retrieved. .PARAMETER Extended Indicates whether to retrieve extended properties of the RAM. If specified, additional properties will be retrieved. .EXAMPLE Get-ComputerRAM -ComputerName "Server01" -Protocol Wsman Retrieves RAM information from a remote computer named Server01 using the Wsman protocol. .EXAMPLE Get-ComputerRAM -ComputerName "WorkstationA" -All Retrieves all available RAM properties from a computer named WorkstationA. #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All, [switch] $Extended ) [string] $Class = 'Win32_physicalmemory ' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = @( 'InstallDate' 'Manufacturer' 'Model' 'OtherIdentifyingInfo' 'PartNumber' 'PoweredOn' 'SerialNumber' 'SKU' 'Tag' 'Version' 'HotSwappable' 'Removable' 'Replaceable' 'FormFactor' 'BankLabel' 'Capacity' 'InterleavePosition' 'MemoryType' 'Speed' 'ConfiguredClockSpeed' 'ConfiguredVoltage' 'DeviceLocator' 'MaxVoltage' 'MinVoltage' 'SMBIOSMemoryType' 'TypeDetail' 'PSComputerName' ) } $FormFactor = @{ '0' = 'Unknown' '1' = 'Other' '2' = 'SIP' '3' = 'DIP' '4' = 'ZIP' '5' = 'SOJ' '6' = 'Proprietary' '7' = 'SIMM' '8' = 'DIMM' '9' = 'TSOP' '10' = 'PGA' '11' = 'RIMM' '12' = 'SODIMM' '13' = 'SRIMM' '14' = 'SMD' '15' = 'SSMP' '16' = 'QFP' '17' = 'TQFP' '18' = 'SOIC' '19' = 'LCC' '20' = 'PLCC' '21' = 'BGA' '22' = 'FPBGA' '23' = 'LGA' } $TypeDetails = @{ '1' = 'Reserved' '2' = 'Other' '4' = 'Unknown' '8' = 'Fast-paged' '16' = 'Static column' '32' = 'Pseudo-static' '64' = 'RAMBUS' '128' = 'Synchronous' '256' = 'CMOS' '512' = 'EDO' '1024' = 'Window DRAM' '2048' = 'Cache DRAM' '4096' = 'Non-volatile' } $InterleavePosition = @{ '0' = "Non-Interleaved" '1' = "First Position" '2' = "Second Position" } $MemoryType = @{ '0' = "Unknown" '1' = "Other" '2' = "DRAM" '3' = "Synchronous DRAM" '4' = "Cache DRAM" '5' = "EDO" '6' = "EDRAM" '7' = "VRAM" '8' = "SRAM" '9' = "ROM" '10' = "ROM" '11' = "FLASH" '12' = "EEPROM" '13' = "FEPROM" '14' = "EPROM" '15' = "CDRAM" '16' = "3DRAM" '17' = "SDRAM" '18' = "SGRAM" '19' = "RDRAM" '20' = "DDR" } $MemoryTypeSMBIOS = @{ '0' = 'Unknown' '1' = 'Other' '2' = 'DRAM' '3' = 'Synchronous DRAM' '4' = 'Cache DRAM' '5' = 'EDO' '6' = 'EDRAM' '7' = 'VRAM' '8' = 'SRAM' '9' = 'RAM' '10' = 'ROM' '11' = 'Flash' '12' = 'EEPROM' '13' = 'FEPROM' '14' = 'EPROM' '15' = 'CDRAM' '16' = '3DRAM' '17' = 'SDRAM' '18' = 'SGRAM' '19' = 'RDRAM' '20' = 'DDR' '21' = 'DDR2' '22' = 'DDR2 FB-DIMM' '24' = 'DDR3' '25' = 'FBD2' '26' = 'DDR4' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { $Ram = [ordered] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Manufacturer = $Data.Manufacturer FormFactor = $FormFactor["$($Data.FormFactor)"] SMBIOSMemoryType = $MemoryTypeSMBIOS["$($Data.SMBIOSMemoryType)"] Size = [math]::round($Data.Capacity / 1GB, 2) Speed = $Data.Speed InterleavePosition = $InterleavePosition["$($Data.InterleavePosition)"] MemoryType = $MemoryType["$($Data.MemoryType)"] TypeDetail = $TypeDetails["$($Data.TypeDetail)"] PartNumber = $Data.PartNumber DeviceLocator = $Data.DeviceLocator } if ($Extended) { $RamExtended = [ordered] @{ InstallDate = $Data.InstallDate Model = $Data.Model OtherIdentifyingInfo = $Data.OtherIdentifyingInfo PoweredOn = $Data.PoweredOn SerialNumber = $Data.SerialNumber SKU = $Data.SKU Tag = $Data.Tag Version = $Data.Version HotSwappable = $Data.HotSwappable Removable = $Data.Removable Replaceable = $Data.Replaceable BankLabel = $Data.BankLabel ConfiguredClockSpeed = $Data.ConfiguredClockSpeed ConfiguredVoltage = $Data.ConfiguredVoltage MaxVoltage = $Data.MaxVoltage MinVoltage = $Data.MinVoltage } [PSCustomObject] ($Ram + $RamExtended) } else { [PSCustomObject] $Ram } } } } } function Get-ComputerRDP { <# .SYNOPSIS Retrieves Remote Desktop Protocol (RDP) settings for a specified computer. .DESCRIPTION This function retrieves RDP settings for a specified computer using the Win32_TSGeneralSetting class. .PARAMETER ComputerName Specifies the name of the computer to retrieve RDP settings for. .EXAMPLE Get-ComputerRDP -ComputerName "Computer01" Retrieves RDP settings for a computer named "Computer01". .EXAMPLE Get-ComputerRDP -ComputerName "Computer02", "Computer03" Retrieves RDP settings for multiple computers named "Computer02" and "Computer03". #> [alias('Get-RDPSecurity')] [cmdletbinding()] param( [string[]] $ComputerName ) $Output = Get-CimData -Class 'Win32_TSGeneralSetting' -NameSpace 'root\cimv2\terminalservices' -ComputerName $ComputerName foreach ($_ in $Output) { $EncryptionLevels = @{ '1' = 'Low' '2' = 'Medium / Client Compatible' '3' = 'High' '4' = 'FIPS Compliant' } $PolicyConfiguredBy = @{ '0' = 'Server' '1' = 'Group policy' '2' = 'Default' } $SecurityLayers = @{ '1' = 'RDP Security Layer' '2' = 'Negotiate' '3' = 'SSL' '4' = 'NEWTBD' } $HashType = @{ '0' = 'Not valid' '1' = 'Self-signed' '2' = 'Custom' } $Connectivity = Test-ComputerPort -ComputerName $_.PSComputerName -PortTCP 3389 -WarningAction SilentlyContinue [PSCustomObject] @{ ComputerName = $_.PSComputerName Name = $_.TerminalName Connectivity = $Connectivity.Status ConnectivitySummary = $Connectivity.Summary SecurityLayer = $SecurityLayers["$($_.SecurityLayer)"] MinimalEncryptionLevel = $EncryptionLevels["$($_.MinEncryptionLevel)"] MinimalEncryptionLevelValue = $_.MinEncryptionLevel PolicySourceUserAuthenticationRequired = $PolicyConfiguredBy["$($_.PolicySourceUserAuthenticationRequired)"] PolicySourceMinimalEncryptionLevel = $PolicyConfiguredBy["$($_.PolicySourceMinEncryptionLevel)"] PolicySourceSecurityLayer = $PolicyConfiguredBy["$($_.PolicySourceSecurityLayer)"] CertificateName = $_.CertificateName CertificateThumbprint = $_.SSLCertificateSHA1Hash CertificateType = $HashType["$($_.SSLCertificateSHA1HashType)"] Transport = $_.Transport Protocol = $_.TerminalProtocol UserAuthenticationRequired = [bool] $_.UserAuthenticationRequired WindowsAuthentication = [bool] $_.WindowsAuthentication } } } function Get-ComputerRoles { <# .SYNOPSIS Get Computer/Server Roles .DESCRIPTION Get Computer/Server Roles .PARAMETER ComputerName Parameter description .PARAMETER FeatureType Display all or limited types. Choices are Role, Role Service and Feature. .PARAMETER EnabledOnly Display only enabled/installed features or roles .EXAMPLE Get-ComputerRoles -ComputerName AD1 -EnabledOnly -FeatureType Role | Format-Table .NOTES General notes #> [alias('Get-ServerRoles')] [CmdletBinding()] param ( [string[]] $ComputerName = $env:COMPUTERNAME, [ValidateSet('Role', 'Role Service', 'Feature')] $FeatureType, [switch] $EnabledOnly ) if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } foreach ($Computer in $ComputerName) { try { $Output = Get-WindowsFeature -ComputerName $Computer -ErrorAction Stop } catch [System.Exception] { if ($_.FullyQualifiedErrorId -like 'UnSupportedTargetDevice,*') { $output = Invoke-Command -ComputerName $computer { Import-Module ServerManager Get-WindowsFeature } } } foreach ($Data in $Output) { if ($EnabledOnly -and $Data.Installed -eq $false) { continue } if ($FeatureType) { if ($Data.FeatureType -notin $FeatureType) { continue } } [PSCustomObject] @{ ComputerName = $Computer Name = $Data.Name DisplayName = $Data.DisplayName FeatureType = $Data.FeatureType Installed = $Data.Installed Description = $Data.Description } } } if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } } function Get-ComputerService { <# .SYNOPSIS Retrieves information about services running on specified computers. .DESCRIPTION This function retrieves information about services running on one or more specified computers. It returns details such as ComputerName, Name, Displayname, Status, and StartType of the services. .EXAMPLE Get-ComputerServices -ComputerName "Computer01" Retrieves information about services running on a single computer named "Computer01". .EXAMPLE Get-ComputerServices -ComputerName "Computer01", "Computer02" Retrieves information about services running on multiple computers named "Computer01" and "Computer02". #> [alias('Get-ComputerServices')] [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) Process { foreach ($Computer in $ComputerName) { $Services = Get-PSService -ComputerName $Computer | Select-Object ComputerName, Name, Displayname, Status, StartType $Services } } } function Get-ComputerSMB { <# .SYNOPSIS Retrieves SMB server configuration details for a specified computer. .DESCRIPTION This function retrieves the SMB server configuration details for a specified computer. .PARAMETER ComputerName Specifies the name of the computer for which to retrieve SMB server configuration details. .EXAMPLE Get-ComputerSMB -ComputerName "Server01" Retrieves the SMB server configuration details for the computer named "Server01". .NOTES This function requires administrative privileges to retrieve SMB server configuration details. #> [CmdletBinding()] param( [string[]] $ComputerName ) [Array] $CollectionComputers = $ComputerName.Where( { $_ -eq $Env:COMPUTERNAME }, 'Split') $SMB = @( if ($CollectionComputers[0].Count -gt 0) { $Output = Get-SmbServerConfiguration foreach ($_ in $Output) { [PSCustomObject] @{ ComputerName = $Env:COMPUTERNAME AnnounceComment = $_.AnnounceComment AnnounceServer = $_.AnnounceServer AsynchronousCredits = $_.AsynchronousCredits AuditSmb1Access = $_.AuditSmb1Access AutoDisconnectTimeout = $_.AutoDisconnectTimeout AutoShareServer = $_.AutoShareServer AutoShareWorkstation = $_.AutoShareWorkstation CachedOpenLimit = $_.CachedOpenLimit DurableHandleV2TimeoutInSeconds = $_.DurableHandleV2TimeoutInSeconds EnableAuthenticateUserSharing = $_.EnableAuthenticateUserSharing EnableDownlevelTimewarp = $_.EnableDownlevelTimewarp EnableForcedLogoff = $_.EnableForcedLogoff EnableLeasing = $_.EnableLeasing EnableMultiChannel = $_.EnableMultiChannel EnableOplocks = $_.EnableOplocks EnableSecuritySignature = $_.EnableSecuritySignature EnableSMB1Protocol = $_.EnableSMB1Protocol EnableSMB2Protocol = $_.EnableSMB2Protocol EnableStrictNameChecking = $_.EnableStrictNameChecking EncryptData = $_.EncryptData IrpStackSize = $_.IrpStackSize KeepAliveTime = $_.KeepAliveTime MaxChannelPerSession = $_.MaxChannelPerSession MaxMpxCount = $_.MaxMpxCount MaxSessionPerConnection = $_.MaxSessionPerConnection MaxThreadsPerQueue = $_.MaxThreadsPerQueue MaxWorkItems = $_.MaxWorkItems NullSessionPipes = $_.NullSessionPipes NullSessionShares = $_.NullSessionShares OplockBreakWait = $_.OplockBreakWait PendingClientTimeoutInSeconds = $_.PendingClientTimeoutInSeconds RejectUnencryptedAccess = $_.RejectUnencryptedAccess RequireSecuritySignature = $_.RequireSecuritySignature ServerHidden = $_.ServerHidden Smb2CreditsMax = $_.Smb2CreditsMax Smb2CreditsMin = $_.Smb2CreditsMin SmbServerNameHardeningLevel = $_.SmbServerNameHardeningLevel TreatHostAsStableStorage = $_.TreatHostAsStableStorage ValidateAliasNotCircular = $_.ValidateAliasNotCircular ValidateShareScope = $_.ValidateShareScope ValidateShareScopeNotAliased = $_.ValidateShareScopeNotAliased ValidateTargetName = $_.ValidateTargetName } } } if ($CollectionComputers[1].Count -gt 0) { $Output = Get-SmbServerConfiguration -CimSession $CollectionComputers[1] foreach ($_ in $Output) { [PSCustomObject] @{ ComputerName = $_.PSComputerName AnnounceComment = $_.AnnounceComment AnnounceServer = $_.AnnounceServer AsynchronousCredits = $_.AsynchronousCredits AuditSmb1Access = $_.AuditSmb1Access AutoDisconnectTimeout = $_.AutoDisconnectTimeout AutoShareServer = $_.AutoShareServer AutoShareWorkstation = $_.AutoShareWorkstation CachedOpenLimit = $_.CachedOpenLimit DurableHandleV2TimeoutInSeconds = $_.DurableHandleV2TimeoutInSeconds EnableAuthenticateUserSharing = $_.EnableAuthenticateUserSharing EnableDownlevelTimewarp = $_.EnableDownlevelTimewarp EnableForcedLogoff = $_.EnableForcedLogoff EnableLeasing = $_.EnableLeasing EnableMultiChannel = $_.EnableMultiChannel EnableOplocks = $_.EnableOplocks EnableSecuritySignature = $_.EnableSecuritySignature EnableSMB1Protocol = $_.EnableSMB1Protocol EnableSMB2Protocol = $_.EnableSMB2Protocol EnableStrictNameChecking = $_.EnableStrictNameChecking EncryptData = $_.EncryptData IrpStackSize = $_.IrpStackSize KeepAliveTime = $_.KeepAliveTime MaxChannelPerSession = $_.MaxChannelPerSession MaxMpxCount = $_.MaxMpxCount MaxSessionPerConnection = $_.MaxSessionPerConnection MaxThreadsPerQueue = $_.MaxThreadsPerQueue MaxWorkItems = $_.MaxWorkItems NullSessionPipes = $_.NullSessionPipes NullSessionShares = $_.NullSessionShares OplockBreakWait = $_.OplockBreakWait PendingClientTimeoutInSeconds = $_.PendingClientTimeoutInSeconds RejectUnencryptedAccess = $_.RejectUnencryptedAccess RequireSecuritySignature = $_.RequireSecuritySignature ServerHidden = $_.ServerHidden Smb2CreditsMax = $_.Smb2CreditsMax Smb2CreditsMin = $_.Smb2CreditsMin SmbServerNameHardeningLevel = $_.SmbServerNameHardeningLevel TreatHostAsStableStorage = $_.TreatHostAsStableStorage ValidateAliasNotCircular = $_.ValidateAliasNotCircular ValidateShareScope = $_.ValidateShareScope ValidateShareScopeNotAliased = $_.ValidateShareScopeNotAliased ValidateTargetName = $_.ValidateTargetName } } } ) $SMB } function Get-ComputerSMBShare { <# .SYNOPSIS Retrieves SMB shares information from specified computers. .DESCRIPTION The Get-ComputerSMBShare function retrieves SMB share information from the specified computers. It can return basic share details or detailed information based on the 'Translated' switch. .PARAMETER ComputerName Specifies the names of the computers from which to retrieve SMB share information. .PARAMETER Translated Indicates whether to return detailed translated information about the SMB shares. .EXAMPLE Get-ComputerSMBShare -ComputerName "Server01" -Translated Retrieves detailed translated information about SMB shares from Server01. .EXAMPLE Get-ComputerSMBShare -ComputerName "Server01", "Server02" Retrieves basic SMB share information from Server01 and Server02. #> [CmdletBinding()] param( [string[]] $ComputerName, [switch] $Translated ) [Array] $CollectionComputers = Get-ComputerSplit -ComputerName $ComputerName if ($CollectionComputers[0].Count -gt 0) { $Output = Get-SmbShare foreach ($O in $Output) { if (-not $Translated) { Add-Member -InputObject $_ -Name 'PSComputerName' -Value $Env:COMPUTERNAME -MemberType NoteProperty -Force $O } else { [PSCustomObject] @{ Name = $O.Name ScopeName = $O.ScopeName Path = $O.Path Description = $O.Description ComputerName = $O.PSComputerName PresetPathAcl = $O.PresetPathAcl ShareState = $O.ShareState.ToString() AvailabilityType = $O.AvailabilityType.ToString() ShareType = $O.ShareType.ToString() FolderEnumerationMode = $O.FolderEnumerationMode.ToString() CachingMode = $O.CachingMode.ToString() LeasingMode = $O.LeasingMode.ToString() QoSFlowScope = $O.QoSFlowScope SmbInstance = $O.SmbInstance.ToString() CATimeout = $O.CATimeout ConcurrentUserLimit = $O.ConcurrentUserLimit ContinuouslyAvailable = $O.ContinuouslyAvailable CurrentUsers = $O.CurrentUsers EncryptData = $O.EncryptData Scoped = $O.Scoped SecurityDescriptor = $O.SecurityDescriptor ShadowCopy = $O.ShadowCopy Special = $O.Special Temporary = $O.Temporary Volume = $O.Volume } } } } if ($CollectionComputers[1].Count -gt 0) { $Output = Get-SmbShare -CimSession $CollectionComputers[1] foreach ($O in $Output) { if (-not $Translated) { $O } else { [PSCustomObject] @{ Name = $O.Name ScopeName = $O.ScopeName Path = $O.Path Description = $O.Description ComputerName = $O.PSComputerName PresetPathAcl = $O.PresetPathAcl ShareState = $O.ShareState.ToString() AvailabilityType = $O.AvailabilityType.ToString() ShareType = $O.ShareType.ToString() FolderEnumerationMode = $O.FolderEnumerationMode.ToString() CachingMode = $O.CachingMode.ToString() LeasingMode = $O.LeasingMode QoSFlowScope = $O.QoSFlowScope SmbInstance = $O.SmbInstance.ToString() CATimeout = $O.CATimeout ConcurrentUserLimit = $O.ConcurrentUserLimit ContinuouslyAvailable = $O.ContinuouslyAvailable CurrentUsers = $O.CurrentUsers EncryptData = $O.EncryptData Scoped = $O.Scoped SecurityDescriptor = $O.SecurityDescriptor ShadowCopy = $O.ShadowCopy Special = $O.Special Temporary = $O.Temporary Volume = $O.Volume } } } } } function Get-ComputerSMBShareList { <# .SYNOPSIS Enumerate shares on a remote or local host and returns the name, type, and special remark for those shares. .DESCRIPTION Enumerate shares on a remote or local host and returns the name, type, and special remark for those shares. Doesnt return the permissions on the share, or logging to given computer Similar to 'net view /All \\ComputerName' .PARAMETER ComputerName The host to enumerate the shares for. Can be accepted as pipeline input by value. .PARAMETER Name The name of the share to filter on. Can be accepted as pipeline input by value. .OUTPUTS [PSCustomObject]@{ ComputerName = [String]'The computer the share relates to' Name = [String]'The name of the share' Path = [string]'\\ComputerName\Name\' Type = [Win32Share.ShareType] An flag enum of the share properties, can be Disk = Disk drive share PrintQueue = Print queue share CommunicationDevice = Communication device share Ipc = Interprocess communication share Temporary = A temporary share Special = Typically a special/admin share like IPC$, C$, ADMIN$ Remark = [String]'More info on the share' TotalBytes = [System.Nullable[int]] TotalFreeBytes = [System.Nullable[int]] FreeBytesAvailableToUser = [System.Nullable[int]] } .LINK https://gist.github.com/jborean93/017d3d890ae8d33276a08d3f5cc7eb45 .EXAMPLE Get-ComputerSMBShareList -ComputerName some-host .EXAMPLE Get-ComputerSMBShareList -ComputerName "DC1" | ft -AutoSize .NOTES Original author: Jordan Borean (@jborean93) Modified by: Matt Cargile (@mattcargile) Modified by: Przemyslaw Klys #> [CmdletBinding(DefaultParameterSetName = 'ComputerName')] param ( [Parameter(Mandatory, ParameterSetName = 'ComputerName', Position = 0)] [string[]] $ComputerName, [Parameter(ValueFromPipeline, ParameterSetName = 'Pipeline')] [string] $InputObject, [Parameter(ParameterSetName = 'ComputerName', Position = 1)] [Parameter(ParameterSetName = 'Pipeline')] [SupportsWildcards()][Alias('ShareName')][string[]] $Name, [switch] $SkipDiskSpace ) begin { if (-not ('Win32Share.NativeMethods' -as [type])) { Add-Type -ErrorAction 'Stop' -TypeDefinition @' using System; using System.Runtime.InteropServices; namespace Win32Share { public class NativeHelpers { [StructLayout(LayoutKind.Sequential)] public struct SHARE_INFO_1 { [MarshalAs(UnmanagedType.LPWStr)] public string shi1_netname; public ShareType shi1_type; [MarshalAs(UnmanagedType.LPWStr)] public string shi1_remark; } } public class NativeMethods { [DllImport("Netapi32.dll")] public static extern UInt32 NetApiBufferFree( IntPtr Buffer); [DllImport("Netapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern Int32 NetShareEnum( string servername, UInt32 level, ref IntPtr bufptr, UInt32 prefmaxlen, ref UInt32 entriesread, ref UInt32 totalentries, ref UInt32 resume_handle); [DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool GetDiskFreeSpaceEx( string lpDirectoryName, ref UInt64 lpFreeBytesAvailableToCaller, ref UInt64 lptotalNumberOfBytes, ref UInt64 lpTotalNumberOfFreeBytes ); } [Flags] public enum ShareType : uint { Disk = 0, PrintQueue = 1, CommunicationDevice = 2, Ipc = 3, Temporary = 0x40000000, Special = 0x80000000, } } '@ } } process { foreach ($compNm in $ComputerName) { Write-Verbose -Message "Get-ComputerSMBShareList - Enumerating shares on '$compNm'" $PSBoundParameters['ComputerName'] = $compNm Get-ComputerSMBInfo @PSBoundParameters } } } function Get-ComputerSMBSharePermissions { <# .SYNOPSIS Retrieves SMB share permissions for specified computers and shares. .DESCRIPTION This function retrieves SMB share permissions for the specified computers and shares. It provides the option to translate the permissions into a more readable format. .PARAMETER ComputerName Specifies the names of the computers to retrieve SMB share permissions from. .PARAMETER ShareName Specifies the names of the shares to retrieve permissions for. .PARAMETER Translated Indicates whether to translate the permissions into a more readable format. .EXAMPLE Get-ComputerSMBSharePermissions -ComputerName "Server1" -ShareName "Share1" -Translated Retrieves SMB share permissions for Server1 and Share1 in a translated format. .EXAMPLE Get-ComputerSMBSharePermissions -ComputerName "Server1", "Server2" -ShareName "Share1", "Share2" Retrieves SMB share permissions for multiple servers and shares. #> [CmdletBinding()] param( [string[]] $ComputerName, [Parameter(Mandatory = $true)][alias('Name')][string[]] $ShareName, [switch] $Translated ) [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName if ($Computers[0].Count -gt 0) { foreach ($Share in $ShareName) { try { $Output = Get-SmbShareAccess -Name $Share -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message Write-Warning -Message "Get-ComputerSMBSharePermissions - Computer $Env:COMPUTERNAME, Share $Share, Error: $ErrorMessage" } foreach ($O in $Output) { if (-not $Translated) { $O | Add-Member -Name 'PSComputerName' -Value $Env:COMPUTERNAME -MemberType NoteProperty -Force $O } else { $Identity = Convert-Identity -Identity $O.AccountName [PSCustomObject] @{ Name = $O.Name ScopeName = $O.ScopeName AccountName = $Identity.Name AccountDomain = $Identity.Domain AccountSID = $Identity.SID AccountType = $Identity.Type AccountError = $Identity.Error AccessControlType = $O.AccessControlType.ToString() AccessRight = $O.AccessRight.ToString() ComputerName = $Env:COMPUTERNAME } } } } } if ($Computers[1].Count -gt 0) { foreach ($Share in $ShareName) { try { $Output = Get-SmbShareAccess -CimSession $Computers[1] -Name $Share -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message Write-Warning -Message "Get-ComputerSMBSharePermissions - Computer $($Computers[1]), Share $Share, Error: $ErrorMessage" } foreach ($O in $Output) { if (-not $Translated) { $O } else { $Identity = Convert-Identity -Identity $O.AccountName [PSCustomObject] @{ Name = $O.Name ScopeName = $O.ScopeName AccountName = $Identity.Name AccountDomain = $Identity.Domain AccountSID = $Identity.SID AccountType = $Identity.Type AccountError = $Identity.Error AccessControlType = $O.AccessControlType.ToString() AccessRight = $O.AccessRight.ToString() ComputerName = $O.PSComputerName } } } } } } function Get-ComputerStartup { <# .SYNOPSIS Retrieves information about startup programs on a remote computer. .DESCRIPTION The Get-ComputerStartup function retrieves information about startup programs on a specified computer using CIM/WMI. .PARAMETER ComputerName Specifies the name of the computer to retrieve startup information from. Defaults to the local computer. .PARAMETER Protocol Specifies the protocol to use for the connection. Valid values are 'Default', 'Dcom', or 'Wsman'. Default is 'Default'. .PARAMETER All Indicates whether to retrieve all properties of the startup programs. .EXAMPLE Get-ComputerStartup -ComputerName "RemoteComputer" -Protocol Wsman Retrieves startup program information from a remote computer using the Wsman protocol. .EXAMPLE Get-ComputerStartup -All Retrieves all startup program information from the local computer. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'win32_startupCommand' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Caption', 'Description', 'Command', 'Location', 'Name', 'User', 'UserSID', 'PSComputerName' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Caption = $Data.Caption Description = $Data.Description Command = $Data.Command Location = $Data.Location Name = $Data.Name User = $Data.User UserSID = $Data.UserSID } } } } } function Get-ComputerSystem { <# .SYNOPSIS Retrieves computer system information from remote computers. .DESCRIPTION This function retrieves computer system information from remote computers using CIM/WMI queries. .PARAMETER ComputerName Specifies the names of the remote computers to retrieve system information from. .PARAMETER Protocol Specifies the protocol to use for the remote connection. Valid values are 'Default', 'Dcom', or 'Wsman'. .PARAMETER All Indicates whether to retrieve all available properties of the computer system. .EXAMPLE Get-ComputerSystem -ComputerName AD1, AD2, EVO1, ADFFS | ft -a * Retrieves computer system information for the specified computers and displays it in a table format. .NOTES This function uses CIM/WMI queries to gather system information from remote computers. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $All ) [string] $Class = 'Win32_ComputerSystem' if ($All) { $Properties = '*' } else { $Properties = 'PSComputerName', 'Name', 'Manufacturer' , 'Domain', 'Model' , 'Systemtype', 'PrimaryOwnerName', 'PCSystemType', 'PartOfDomain', 'CurrentTimeZone', 'BootupState', 'Roles', 'SystemFamily' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Name = $Data.Name Manufacturer = $Data.Manufacturer Domain = $Data.Domain Model = $Data.Model Systemtype = $Data.Systemtype PrimaryOwnerName = $Data.PrimaryOwnerName PCSystemType = [Microsoft.PowerShell.Commands.PCSystemType] $Data.PCSystemType PartOfDomain = $Data.PartOfDomain CurrentTimeZone = $Data.CurrentTimeZone BootupState = $Data.BootupState SystemFamily = $Data.SystemFamily Roles = $Data.Roles -join ', ' } } } } } function Get-ComputerTask { <# .SYNOPSIS Get Task Schedule information .DESCRIPTION Get Task Schedule information .PARAMETER ComputerName Specifies computer on which you want to run the operation. .EXAMPLE Get-ComputerTask | Format-Table .NOTES General notes #> [alias('Get-ComputerTasks')] [cmdletbinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) foreach ($Computer in $ComputerName) { try { $LocalComputerDNSName = [System.Net.Dns]::GetHostByName($Env:COMPUTERNAME).HostName } catch { $LocalComputerDNSName = $Computer } if ($Computer -eq $Env:COMPUTERNAME -or $Computer -eq $LocalComputerDNSName) { $TaskParameters = @{} } else { $TaskParameters = @{ CimSession = $Computer } } $Tasks = Get-ScheduledTask @TaskParameters foreach ($Task in $Tasks) { $Info = $Task | Get-ScheduledTaskInfo @TaskParameters $Actions = foreach ($_ in $Task.Actions) { -join ($_.Execute, $_.Arguments) } [PSCustomObject] @{ ComputerName = $Computer TaskName = $Task.TaskName TaskPath = $Task.TaskPath State = $Task.State Actions = $Actions Author = $Task.Author Date = $Task.Date Description = $Task.Description Documentation = $Task.Documentation PrincipalDisplayName = $Task.Principal.DisplayName PrincipalUserID = $Task.Principal.UserID PrincipalGroupID = $Task.Principal.GroupID PrincipalLogonType = $Task.Principal.LogonType PrincipalRunLevel = $Task.Principal.RunLevel PrincipalProcessTokenSidType = $Task.Principal.ProcessTokenSidType PrincipalRequiredPrivilege = $Task.Principal.RequiredPrivilege SettingsAllowDemandStart = $Task.Settings.AllowDemandStart SettingsAllowHardTerminate = $Task.Settings.AllowHardTerminate SettingsCompatibility = $Task.Settings.Compatibility SettingsDeleteExpiredTaskAfter = $Task.Settings.DeleteExpiredTaskAfter SettingsDisallowStartIfOnBatteries = $Task.Settings.DisallowStartIfOnBatteries SettingsEnabled = $Task.Settings.Enabled SettingsExecutionTimeLimit = $Task.Settings.ExecutionTimeLimit SettingsHidden = $Task.Settings.Hidden SettingsIdleSettings = $Task.Settings.IdleSettings SettingsMultipleInstances = $Task.Settings.MultipleInstances SettingsNetworkSettings = $Task.Settings.NetworkSettings SettingsPriority = $Task.Settings.Priority SettingsRestartCount = $Task.Settings.RestartCount SettingsRestartInterval = $Task.Settings.RestartInterval SettingsRunOnlyIfIdle = $Task.Settings.RunOnlyIfIdle SettingsRunOnlyIfNetworkAvailable = $Task.Settings.RunOnlyIfNetworkAvailable SettingsStartWhenAvailable = $Task.Settings.StartWhenAvailable SettingsStopIfGoingOnBatteries = $Task.Settings.StopIfGoingOnBatteries SettingsWakeToRun = $Task.Settings.WakeToRun SettingsDisallowStartOnRemoteAppSession = $Task.Settings.DisallowStartOnRemoteAppSession SettingsUseUnifiedSchedulingEngine = $Task.Settings.UseUnifiedSchedulingEngine SettingsMaintenanceSettings = $Task.Settings.MaintenanceSettings SettingsVolatile = $Task.Settings.volatile Source = $Task.Source URI = $Task.URI Version = $Task.Version LastRunTime = $Info.LastRunTime LastTaskResult = $Info.LastTaskResult NextRunTime = $Info.NextRunTime NumberOfMissedRuns = $Info.NumberOfMissedRuns } } } } function Get-ComputerTime { <# .SYNOPSIS Gets time difference between computers and time source including boot time .DESCRIPTION Gets time difference between computers and time source including boot time .PARAMETER TimeSource Parameter description .PARAMETER Domain Parameter description .PARAMETER TimeTarget Specifies computer on which you want to run the CIM operation. You can specify a fully qualified domain name (FQDN), a NetBIOS name, or an IP address. If you do not specify this parameter, the cmdlet performs the operation on the local computer using Component Object Model (COM). .PARAMETER Credential Specifies a user account that has permission to perform this action. The default is the current user. .PARAMETER ForceCIM .PARAMETER ToLocal .EXAMPLE Get-ComputerTime -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize Output Name LocalDateTime RemoteDateTime InstallTime LastBootUpTime TimeDifferenceMinutes TimeDifferenceSeconds TimeDifferenceMilliseconds TimeSourceName ---- ------------- -------------- ----------- -------------- --------------------- --------------------- -------------------------- -------------- AD2 13.08.2019 23:40:26 13.08.2019 23:40:26 30.05.2018 18:30:48 09.08.2019 18:40:31 8,33333333333333E-05 0,005 5 AD1.ad.evotec.xyz AD3 13.08.2019 23:40:26 13.08.2019 17:40:26 26.05.2019 17:30:17 09.08.2019 18:40:30 0,000266666666666667 0,016 16 AD1.ad.evotec.xyz EVOWin 13.08.2019 23:40:26 13.08.2019 23:40:26 24.05.2019 22:46:45 09.08.2019 18:40:06 6,66666666666667E-05 0,004 4 AD1.ad.evotec.xyz .EXAMPLE Get-ComputerTime -TimeSource AD1 -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize .EXAMPLE Get-ComputerTime -TimeSource 'pool.ntp.org' -TimeTarget AD2, AD3, EVOWin | Format-Table -AutoSize .NOTES General notes #> [CmdletBinding()] param( [string] $TimeSource, [string] $Domain = $Env:USERDNSDOMAIN, [alias('ComputerName')][string[]] $TimeTarget = $ENV:COMPUTERNAME, [pscredential] $Credential, [switch] $ForceCIM ) if (-not $TimeSource) { $TimeSource = (Get-ADDomainController -Discover -Service PrimaryDC -DomainName $Domain).HostName } if ($ForceCIM) { $TimeSourceInformation = Get-CimData -ComputerName $TimeSource -Class 'win32_operatingsystem' -Credential $Credential if ($TimeSourceInformation.LocalDateTime) { $TimeSourceInformation = $TimeSourceInformation.LocalDateTime } else { $TimeSourceInformation = $null } } else { $TimeSourceInformation = Get-ComputerTimeNtp -Server $TimeSource -ToLocal } $TimeTargetInformationCache = @{ } $TimeTargetInformation = Get-CimData -ComputerName $TimeTarget -Class 'win32_operatingsystem' -Credential $Credential foreach ($_ in $TimeTargetInformation) { $TimeTargetInformationCache[$_.PSComputerName] = $_ } $TimeLocalCache = @{ } $TimeLocal = Get-CimData -ComputerName $TimeTarget -Class 'Win32_LocalTime' -Credential $Credential foreach ($_ in $TimeLocal) { $TimeLocalCache[$_.PSComputerName] = $_ } $AllResults = foreach ($Computer in $TimeTarget) { $WMIComputerTime = $TimeLocalCache[$Computer] $WMIComputerTarget = $TimeTargetInformationCache[$Computer] if ($WMIComputerTime -and $WMIComputerTime.Year -and $WMIComputerTime.Month) { $RemoteDateTime = Get-Date -Year $WMIComputerTime.Year -Month $WMIComputerTime.Month -Day $WMIComputerTime.Day -Hour $WMIComputerTime.Hour -Minute $WMIComputerTime.Minute -Second $WMIComputerTime.Second } else { $RemoteDateTIme = '' } if ($WMIComputerTarget.LocalDateTime -and $TimeSourceInformation) { $Result = New-TimeSpan -Start $TimeSourceInformation -End $WMIComputerTarget.LocalDateTime $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime [PSCustomObject] @{ Name = $Computer LocalDateTime = $WMIComputerTarget.LocalDateTime RemoteDateTime = $RemoteDateTime InstallTime = $WMIComputerTarget.InstallDate LastBootUpTime = $WMIComputerTarget.LastBootUpTime LastBootUpTimeInDays = if ($null -ne $ResultFromBoot.TotalDays) { [math]::Round($ResultFromBoot.TotalDays, 2) } else { $null } TimeDifferenceMinutes = if ($Result.TotalMinutes -lt 0) { ($Result.TotalMinutes * -1) } else { $Result.TotalMinutes } TimeDifferenceSeconds = if ($Result.TotalSeconds -lt 0) { ($Result.TotalSeconds * -1) } else { $Result.TotalSeconds } TimeDifferenceMilliseconds = if ($Result.TotalMilliseconds -lt 0) { ($Result.TotalMilliseconds * -1) } else { $Result.TotalMilliseconds } TimeSourceName = $TimeSource Status = '' } } else { if ($WMIComputerTarget.LastBootUpTime) { $ResultFromBoot = New-TimeSpan -Start $WMIComputerTarget.LastBootUpTime -End $WMIComputerTarget.LocalDateTime } else { $ResultFromBoot = $null } [PSCustomObject] @{ Name = $Computer LocalDateTime = $WMIComputerTarget.LocalDateTime RemoteDateTime = $RemoteDateTime InstallTime = $WMIComputerTarget.InstallDate LastBootUpTime = $WMIComputerTarget.LastBootUpTime LastBootUpTimeInDays = if ($ResultFromBoot) { [math]::Round($ResultFromBoot.TotalDays, 2) } else { $null } TimeDifferenceMinutes = $null TimeDifferenceSeconds = $null TimeDifferenceMilliseconds = $null TimeSourceName = $TimeSource Status = 'Unable to get time difference.' } } } $AllResults } function Get-ComputerTimeNtp { <# .Synopsis Gets (Simple) Network Time Protocol time (SNTP/NTP, rfc-1305, rfc-2030) from a specified server .DESCRIPTION This function connects to an NTP server on UDP port 123 and retrieves the current NTP time. Selected components of the returned time information are decoded and returned in a PSObject. .PARAMETER Server The NTP Server to contact. Uses pool.ntp.org by default. .EXAMPLE Get-NtpTime uk.pool.ntp.org Gets time from the specified server. .EXAMPLE Get-NtpTime | fl * Get time from default server (pool.ntp.org) and displays all output object attributes. .FUNCTIONALITY Gets NTP time from a specified server. .NOTES Author https://github.com/ChrisWarwick/PowerShell-NTP-Time Slightly simplified for different usage scenarios #> [CmdletBinding()] Param ( [String]$Server = 'pool.ntp.org', [switch]$ToLocal ) $StartOfEpoch = New-Object DateTime(1900, 1, 1, 0, 0, 0, [DateTimeKind]::Utc) [Byte[]]$NtpData = , 0 * 48 $NtpData[0] = 0x1B $Socket = [Net.Sockets.Socket]::new([Net.Sockets.AddressFamily]::InterNetwork, [Net.Sockets.SocketType]::Dgram, [Net.Sockets.ProtocolType]::Udp) $Socket.SendTimeOut = 2000 $Socket.ReceiveTimeOut = 2000 Try { $Socket.Connect($Server, 123) } Catch { $_.Error Write-Warning "Get-ComputerTimeNtp - Failed to connect to server $Server" return } $t1 = Get-Date Try { [Void]$Socket.Send($NtpData) [Void]$Socket.Receive($NtpData) } Catch { Write-Warning "Get-ComputerTimeNtp - Failed to communicate with server $Server" return } $t4 = Get-Date $Socket.Shutdown("Both") $Socket.Close() $LI = ($NtpData[0] -band 0xC0) -shr 6 If ($LI -eq 3) { Write-Warning 'Get-ComputerTimeNtp - Alarm condition from server (clock not synchronized)' return } $IntPart = [BitConverter]::ToUInt32($NtpData[43..40], 0) $FracPart = [BitConverter]::ToUInt32($NtpData[47..44], 0) $t3ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000) $IntPart = [BitConverter]::ToUInt32($NtpData[35..32], 0) $FracPart = [BitConverter]::ToUInt32($NtpData[39..36], 0) $t2ms = $IntPart * 1000 + ($FracPart * 1000 / 0x100000000) $t1ms = ([TimeZoneInfo]::ConvertTimeToUtc($t1) - $StartOfEpoch).TotalMilliseconds $t4ms = ([TimeZoneInfo]::ConvertTimeToUtc($t4) - $StartOfEpoch).TotalMilliseconds $Offset = (($t2ms - $t1ms) + ($t3ms - $t4ms)) / 2 [DateTime] $NTPDateTime = $StartOfEpoch.AddMilliseconds($t4ms + $Offset) if ($ToLocal) { $NTPDateTime.ToLocalTime() } else { $NTPDateTime } } function Get-ComputerWindowsFeatures { <# .SYNOPSIS Get Windows Features status on one or more computers/servers .DESCRIPTION Get Windows Features status on one or more computers/servers .PARAMETER ComputerName ComputerName to provide when executing query remotly. By default current computer name is used. .PARAMETER Protocol Protocol to use when gathering data. Choices are Default, Dcom, WSMan .PARAMETER EnabledOnly Returns only data if Windows Feature is enabled .PARAMETER All Gets all properties without any preprocessing .EXAMPLE Get-ComputerWindowsFeatures -EnabledOnly | Format-Table .NOTES General notes #> [CmdletBinding()] param( [string] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [switch] $EnabledOnly, [switch] $All ) [string] $Class = 'Win32_OptionalFeature' if ($All) { [string] $Properties = '*' } else { [string[]] $Properties = 'Name', 'Caption' , 'Status', 'InstallState', 'InstallDate', 'PSComputerName' } $State = @{ '1' = 'Enabled' '2' = 'Disabled' '3' = 'Absent' '4' = 'Unknown' } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { $Information } else { foreach ($Info in $Information) { foreach ($Data in $Info) { $InstallState = $State["$($Data.InstallState)"] if ($EnabledOnly -and $InstallState -ne 'Enabled') { continue } [PSCustomObject] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Name = $Data.Name Caption = $Data.Caption InstallState = $InstallState } } } } } function Get-ComputerWindowsUpdates { <# .SYNOPSIS Retrieves information about Windows updates installed on specified computers. .DESCRIPTION This function retrieves details about Windows updates installed on one or more computers specified by the ComputerName parameter. .PARAMETER ComputerName Specifies the name of the computer(s) to retrieve Windows update information for. .EXAMPLE Get-ComputerWindowsUpdates -ComputerName "EVOWIN", "AD1" Retrieves Windows update information for computers named "EVOWIN" and "AD1". .NOTES This function uses the Get-HotFix cmdlet to gather information about Windows updates. #> [CmdletBinding()] param( [string[]] $ComputerName = $Env:COMPUTERNAME ) foreach ($Computer in $ComputerName) { try { $Data = Get-HotFix -ComputerName $Computer $Output = foreach ($Update in $Data) { [PSCustomObject] @{ ComputerName = $Computer InstalledOn = $Update.InstalledOn Description = $Update.Description KB = $Update.HotFixId InstalledBy = $Update.InstalledBy Caption = $Update.Caption } } $Output | Sort-Object -Descending InstalledOn } catch { Write-Warning -Message "Get-ComputerWindowsUpdates - No data for computer $($Computer). Failed with errror: $($_.Exception.Message)" } } } function Get-OperatingSystem { <# .SYNOPSIS Retrieves information about Windows operating systems. .DESCRIPTION This function returns details about various versions of Windows operating systems, including their names, version numbers, code names, marketing names, build numbers, release dates, and support end dates. .PARAMETER Version Specifies the version number of the Windows operating system to retrieve information for. .EXAMPLE Get-OperatingSystem -Version '10.0 (19042)' Retrieves information about Windows 10 20H2. .EXAMPLE Get-OperatingSystem Retrieves information about all available Windows operating systems. #> [cmdletbinding()] param( [string] $Version ) $ListOperatingSystems = [ordered] @{ '10.0 (19043)' = [PSCustomObject] @{ Name = 'Windows 10 21H1'; Version = '10.0 (19043)'; CodeName = '21H1'; MarketingName = 'May 2021 Update'; BuildNumber = '19043'; ReleaseDate = (Get-Date -Year 2021 -Month 5 -Day 18 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2022 -Month 12 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2022 -Month 12 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (19042)' = [PSCustomObject] @{ Name = 'Windows 10 20H2'; Version = '10.0 (19042)'; CodeName = '20H2'; MarketingName = 'October 2020 Update'; BuildNumber = '19042'; ReleaseDate = (Get-Date -Year 2020 -Month 9 -Day 20 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2022 -Month 5 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2023 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (19041)' = [PSCustomObject] @{ Name = 'Windows 10 2004'; Version = '10.0 (19041)'; CodeName = '20H1'; MarketingName = 'May 2020 Update'; BuildNumber = '19041'; ReleaseDate = (Get-Date -Year 2020 -Month 5 -Day 27 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2021 -Month 12 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 12 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (18363)' = [PSCustomObject] @{ Name = "Windows 10 1909"; Version = '10.0 (18363)'; CodeName = '19H2'; MarketingName = 'November 2019 Update'; BuildNumber = '18363'; ReleaseDate = (Get-Date -Year 2019 -Month 11 -Day 12 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2022 -Month 5 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (18362)' = [PSCustomObject] @{ Name = "Windows 10 1903"; Version = '10.0 (18362)'; CodeName = '19H1'; MarketingName = 'May 2019 Update'; BuildNumber = '18362'; ReleaseDate = (Get-Date -Year 2019 -Month 5 -Day 21 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 12 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2020 -Month 12 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (17763)' = [PSCustomObject] @{ Name = "Windows 10 1809"; Version = '10.0 (17763)'; CodeName = 'Redstone 5'; MarketingName = 'October 2018 Update'; BuildNumber = '17763'; ReleaseDate = (Get-Date -Year 2018 -Month 11 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 11 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2029 -Month 1 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1) } '10.0 (17134)' = [PSCustomObject] @{ Name = "Windows 10 1803"; Version = '10.0 (17134)'; CodeName = 'Redstone 4'; MarketingName = 'April 2018 Update'; BuildNumber = '17134'; ReleaseDate = (Get-Date -Year 2018 -Month 4 -Day 30 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2020 -Month 11 -Day 12 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2021 -Month 5 -Day 11 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (16299)' = [PSCustomObject] @{ Name = "Windows 10 1709"; Version = '10.0 (16299)'; CodeName = 'Redstone 3'; MarketingName = 'Fall Creators Update'; BuildNumber = '16299'; ReleaseDate = (Get-Date -Year 2017 -Month 9 -Day 17 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2019 -Month 4 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2020 -Month 10 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); ; LTSC = $null } '10.0 (15063)' = [PSCustomObject] @{ Name = "Windows 10 1703"; Version = '10.0 (15063)'; CodeName = 'Redstone 2'; MarketingName = 'Creators Update'; BuildNumber = '15063'; ReleaseDate = (Get-Date -Year 2017 -Month 4 -Day 5 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2018 -Month 10 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2019 -Month 10 -Day 8 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); ; LTSC = $null } '10.0 (14393)' = [PSCustomObject] @{ Name = "Windows 10 1607"; Version = '10.0 (14393)'; CodeName = 'Redstone 1'; MarketingName = 'Anniversary Update'; BuildNumber = '14393'; ReleaseDate = (Get-Date -Year 2016 -Month 8 -Day 2 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2018 -Month 4 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2019 -Month 4 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2026 -Month 10 -Day 13 -Second 1 -Minute 1 -Hour 1 -Millisecond 1) } '10.0 (10586)' = [PSCustomObject] @{ Name = "Windows 10 1511"; Version = '10.0 (10586)'; CodeName = 'Threshold 2'; MarketingName = 'November Update'; BuildNumber = '10586'; ReleaseDate = (Get-Date -Year 2015 -Month 11 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2017 -Month 10 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2018 -Month 4 -Day 10 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = $null } '10.0 (10240)' = [PSCustomObject] @{ Name = "Windows 10 1507"; Version = '10.0 (10240)' ; CodeName = 'Threshold 1'; MarketingName = 'N/A'; BuildNumber = '10240'; ReleaseDate = (Get-Date -Year 2015 -Month 7 -Day 29 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndPro = (Get-Date -Year 2017 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); SupportEndEnterprise = (Get-Date -Year 2017 -Month 5 -Day 9 -Second 1 -Minute 1 -Hour 1 -Millisecond 1); LTSC = (Get-Date -Year 2025 -Month 10 -Day 14 -Second 1 -Minute 1 -Hour 1 -Millisecond 1) } } if ($Version) { $ListOperatingSystems[$Version] } else { $ListOperatingSystems.Values } } function Get-IPAddressInformation { <# .SYNOPSIS Retrieves detailed information about an IP address using the ip-api.com service. .DESCRIPTION This function retrieves detailed information about the specified IP address using the ip-api.com service. It provides details such as country, region, city, ISP, and more. .PARAMETER IP Specifies the IP address for which information needs to be retrieved. .EXAMPLE Get-IpAddressInformation -IP "8.8.8.8" Retrieves information about the IP address "8.8.8.8" using the ip-api.com service. .NOTES This function requires an active internet connection to retrieve IP address information from the ip-api.com service. #> [cmdletbinding()] param( [string] $IP ) try { $Information = Invoke-RestMethod -Method get -Uri "http://ip-api.com/json/$ip" } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Warning "Get-IPAddressInformation - Error occured on IP $IP`: $ErrorMessage" } return $Information } function Get-MyIpAddress { <# .SYNOPSIS Retrieves the public IP address of the current machine using OpenDNS. .DESCRIPTION This function retrieves the public IP address of the current machine by querying OpenDNS servers. It returns the IP address as a string. .EXAMPLE Get-MyIpAddress Retrieves the public IP address of the current machine. .NOTES Author: Your Name Date: Current Date #> [alias('Get-MyIP')] [CmdletBinding()] param() $DNSParam = @{ Name = 'myip.opendns.com' Server = 'resolver1.opendns.com' DnsOnly = $true } return Resolve-DnsName @DNSParam | ForEach-Object IPAddress } function Set-PasswordRemotely { <# .SYNOPSIS Set-PasswordRemotely function changes a user's password on a remote domain controller. .DESCRIPTION The Set-PasswordRemotely function allows changing a user's password securely on a remote domain controller. It requires the username, old password, new password, and optionally the domain controller's DNS name or IP address. .PARAMETER UserName Specifies the username of the account for which the password will be changed. .PARAMETER OldPassword Specifies the old password of the user account. .PARAMETER NewPassword Specifies the new password to set for the user account. .PARAMETER DomainController Specifies the domain controller's DNS name or IP address. If not provided, the function will prompt for it or automatically determine it if the machine is joined to a domain. .EXAMPLE Set-PasswordRemotely -UserName "JohnDoe" -OldPassword $SecureOldPassword -NewPassword $SecureNewPassword -DomainController "DC01" Description: Changes the password for the user account "JohnDoe" on the domain controller "DC01" using the provided old and new passwords. .EXAMPLE Set-PasswordRemotely -UserName "JaneSmith" -OldPassword $SecureOldPassword -NewPassword $SecureNewPassword Description: Changes the password for the user account "JaneSmith" on the domain controller determined automatically, using the provided old and new passwords. #> [CmdletBinding(DefaultParameterSetName = 'Secure')] param( [Parameter(ParameterSetName = 'Secure', Mandatory)][string] $UserName, [Parameter(ParameterSetName = 'Secure', Mandatory)][securestring] $OldPassword, [Parameter(ParameterSetName = 'Secure', Mandatory)][securestring] $NewPassword, [Parameter(ParameterSetName = 'Secure')][alias('DC', 'Server', 'ComputerName')][string] $DomainController ) Begin { $DllImport = @' [DllImport("netapi32.dll", CharSet = CharSet.Unicode)] public static extern bool NetUserChangePassword(string domain, string username, string oldpassword, string newpassword); '@ $NetApi32 = Add-Type -MemberDefinition $DllImport -Name 'NetApi32' -Namespace 'Win32' -PassThru if (-not $DomainController) { if ($env:computername -eq $env:userdomain) { $DomainController = Read-Host -Prompt 'Domain Controller DNS name or IP Address' } else { $Domain = $Env:USERDNSDOMAIN $Context = [System.DirectoryServices.ActiveDirectory.DirectoryContext]::new([System.DirectoryServices.ActiveDirectory.DirectoryContextType]::Domain, $Domain) $DomainController = ([System.DirectoryServices.ActiveDirectory.DomainController]::FindOne($Context)).Name } } } Process { if ($DomainController -and $OldPassword -and $NewPassword -and $UserName) { $OldPasswordPlain = [System.Net.NetworkCredential]::new([string]::Empty, $OldPassword).Password $NewPasswordPlain = [System.Net.NetworkCredential]::new([string]::Empty, $NewPassword).Password $result = $NetApi32::NetUserChangePassword($DomainController, $UserName, $OldPasswordPlain, $NewPasswordPlain) if ($result) { Write-Host -Object "Set-PasswordRemotely - Password change for account $UserName failed on $DomainController. Please try again." -ForegroundColor Red } else { Write-Host -Object "Set-PasswordRemotely - Password change for account $UserName succeeded on $DomainController." -ForegroundColor Cyan } } else { Write-Warning "Set-PasswordRemotely - Password change for account failed. All parameters are required. " } } End { $OldPassword = $null $NewPassword = $null $OldPasswordPlain = $null $NewPasswordPlain = $null [System.GC]::Collect() } } function Convert-BinaryToHex { <# .SYNOPSIS Converts an array of binary numbers to hexadecimal format. .DESCRIPTION This function takes an array of binary numbers and converts them to hexadecimal format. .PARAMETER Binary Specifies the array of binary numbers to be converted to hexadecimal. .EXAMPLE Convert-BinaryToHex -Binary 1101 1010 Converts the binary numbers 1101 and 1010 to hexadecimal format. .EXAMPLE 1101 1010 | Convert-BinaryToHex Converts the binary numbers 1101 and 1010 piped into the function to hexadecimal format. #> param( [alias('Bin')] [Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)] [Byte[]]$Binary ) if ($null -eq $Binary) { return } if ($Binary.Length -eq 1) { $Binary = @($input) } $Return = -join ($Binary | ForEach-Object { "{0:X2}" -f $_ }) $Return } function Convert-BinaryToString { <# .SYNOPSIS Converts an array of binary numbers to a string. .DESCRIPTION This function takes an array of binary numbers and converts them to a string using Unicode encoding. .PARAMETER Binary Specifies the array of binary numbers to be converted to a string. .EXAMPLE Convert-BinaryToString -Binary 01001000 01100101 01101100 01101100 01101111 Converts the binary numbers to the string "Hello". .EXAMPLE 01001000 01100101 01101100 01101100 01101111 | Convert-BinaryToString Converts the binary numbers piped into the function to the string "Hello". #> param( [alias('Bin')] [Parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)] [Byte[]]$Binary ) if ($null -ne $Binary) { [System.Text.Encoding]::Unicode.GetString($Binary) } } function Convert-Color { <# .Synopsis This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX) .Description This color converter gives you the hexadecimal values of your RGB colors and vice versa (RGB to HEX). Use it to convert your colors and prepare your graphics and HTML web pages. .Parameter RBG Enter the Red Green Blue value comma separated. Red: 51 Green: 51 Blue: 204 for example needs to be entered as 51,51,204 .Parameter HEX Enter the Hex value to be converted. Do not use the '#' symbol. (Ex: 3333CC converts to Red: 51 Green: 51 Blue: 204) .Example .\convert-color -hex FFFFFF Converts hex value FFFFFF to RGB .Example .\convert-color -RGB 123,200,255 Converts Red = 123 Green = 200 Blue = 255 to Hex value #> param( [Parameter(ParameterSetName = "RGB", Position = 0)] [ValidateScript( { $_ -match '^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$' })] $RGB, [Parameter(ParameterSetName = "HEX", Position = 0)] [ValidateScript( { $_ -match '[A-Fa-f0-9]{6}' })] [string] $HEX ) switch ($PsCmdlet.ParameterSetName) { "RGB" { if ($null -eq $RGB[2]) { Write-Error "Value missing. Please enter all three values seperated by comma." } $red = [convert]::Tostring($RGB[0], 16) $green = [convert]::Tostring($RGB[1], 16) $blue = [convert]::Tostring($RGB[2], 16) if ($red.Length -eq 1) { $red = '0' + $red } if ($green.Length -eq 1) { $green = '0' + $green } if ($blue.Length -eq 1) { $blue = '0' + $blue } Write-Output $red$green$blue } "HEX" { $red = $HEX.Remove(2, 4) $Green = $HEX.Remove(4, 2) $Green = $Green.remove(0, 2) $Blue = $hex.Remove(0, 4) $Red = [convert]::ToInt32($red, 16) $Green = [convert]::ToInt32($green, 16) $Blue = [convert]::ToInt32($blue, 16) Write-Output $red, $Green, $blue } } } function Convert-CountryCodeToCountry { <# .SYNOPSIS Converts a country code to a country name, or when used with a switch to full culture information .DESCRIPTION Converts a country code to a country name, or when used with a switch to full culture information .PARAMETER CountryCode Country code .PARAMETER All Provide full culture information rather than just the country name .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' -All .EXAMPLE $Test = Convert-CountryCodeToCountry $Test['PL']['Culture'] | fl $Test['PL']['RegionInformation'] .EXAMPLE Convert-CountryCodeToCountry -CountryCode 'PL' Convert-CountryCodeToCountry -CountryCode 'POL' .NOTES General notes #> [cmdletBinding()] param( [string] $CountryCode, [switch] $All ) if ($Script:QuickSearch) { if ($PSBoundParameters.ContainsKey('CountryCode')) { if ($All) { $Script:QuickSearch[$CountryCode] } else { $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName } } else { $Script:QuickSearch } } else { $Script:QuickSearch = [ordered] @{} $AllCultures = [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) foreach ($Culture in $AllCultures) { $RegionInformation = [System.Globalization.RegionInfo]::new($Culture) $Script:QuickSearch[$RegionInformation.TwoLetterISORegionName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } $Script:QuickSearch[$RegionInformation.ThreeLetterISORegionName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } } if ($PSBoundParameters.ContainsKey('CountryCode')) { if ($All) { $Script:QuickSearch[$CountryCode] } else { $Script:QuickSearch[$CountryCode].RegionInformation.EnglishName } } else { $Script:QuickSearch } } } function Convert-CountryToContinent { <# .SYNOPSIS Convert country to continent .DESCRIPTION Convert country to continent or return a hashtable of countries and their corresponding continent. If the country is not found (for example empty), it will return "Unknown" .PARAMETER Country Country to convert. If country is not given it will return a hashtable of countries and their corresponding continent. .EXAMPLE Convert-CountryToContinent -Country "Poland" .EXAMPLE Convert-CountryToContinent .NOTES General notes #> [CmdletBinding()] param( [string] $Country, [switch] $ReturnHashTable ) $CountryToContinent = [ordered] @{ "Afghanistan" = "Asia" "Albania" = "Europe" "Algeria" = "Africa" "Andorra" = "Europe" "Angola" = "Africa" "Antigua and Barbuda" = "North America" "Argentina" = "South America" "Armenia" = "Asia" "Australia" = "Australia/Oceania" "Austria" = "Europe" "Azerbaijan" = "Asia" "Bahamas" = "North America" "Bahrain" = "Asia" "Bangladesh" = "Asia" "Barbados" = "North America" "Belarus" = "Europe" "Belgium" = "Europe" "Belize" = "North America" "Benin" = "Africa" "Bhutan" = "Asia" "Bolivia" = "South America" "Bosnia and Herzegovina" = "Europe" "Botswana" = "Africa" "Brazil" = "South America" "Brunei" = "Asia" "Bulgaria" = "Europe" "Burkina Faso" = "Africa" "Burundi" = "Africa" "Cabo Verde" = "Africa" "Cambodia" = "Asia" "Cameroon" = "Africa" "Canada" = "North America" "Central African Republic" = "Africa" "Chad" = "Africa" "Chile" = "South America" "China" = "Asia" "Colombia" = "South America" "Comoros" = "Africa" "Congo, Democratic Republic of the" = "Africa" "Congo, Republic of the" = "Africa" "Costa Rica" = "North America" "Cote d'Ivoire" = "Africa" "Croatia" = "Europe" "Cuba" = "North America" "Cyprus" = "Asia" "Czechia" = "Europe" "Denmark" = "Europe" "Djibouti" = "Africa" "Dominica" = "North America" "Dominican Republic" = "North America" "Ecuador" = "South America" "Egypt" = "Africa" "El Salvador" = "North America" "Equatorial Guinea" = "Africa" "Eritrea" = "Africa" "Estonia" = "Europe" "Eswatini" = "Africa" "Ethiopia" = "Africa" "Fiji" = "Australia/Oceania" "Finland" = "Europe" "France" = "Europe" "Gabon" = "Africa" "Gambia" = "Africa" "Georgia" = "Asia" "Germany" = "Europe" "Ghana" = "Africa" "Greece" = "Europe" "Grenada" = "North America" "Guatemala" = "North America" "Guinea" = "Africa" "Guinea-Bissau" = "Africa" "Guyana" = "South America" "Haiti" = "North America" "Honduras" = "North America" "Hungary" = "Europe" "Iceland" = "Europe" "India" = "Asia" "Indonesia" = "Asia" "Iran" = "Asia" "Iraq" = "Asia" "Ireland" = "Europe" "Israel" = "Asia" "Italy" = "Europe" "Jamaica" = "North America" "Japan" = "Asia" "Jordan" = "Asia" "Kazakhstan" = "Asia" "Kenya" = "Africa" "Kiribati" = "Australia/Oceania" "Kosovo" = "Europe" "Kuwait" = "Asia" "Kyrgyzstan" = "Asia" "Laos" = "Asia" "Latvia" = "Europe" "Lebanon" = "Asia" "Lesotho" = "Africa" "Liberia" = "Africa" "Libya" = "Africa" "Liechtenstein" = "Europe" "Lithuania" = "Europe" "Luxembourg" = "Europe" "Madagascar" = "Africa" "Malawi" = "Africa" "Malaysia" = "Asia" "Maldives" = "Asia" "Mali" = "Africa" "Malta" = "Europe" "Marshall Islands" = "Australia/Oceania" "Mauritania" = "Africa" "Mauritius" = "Africa" "Mexico" = "North America" "Micronesia" = "Australia/Oceania" "Moldova" = "Europe" "Monaco" = "Europe" "Mongolia" = "Asia" "Montenegro" = "Europe" "Morocco" = "Africa" "Mozambique" = "Africa" "Myanmar" = "Asia" "Namibia" = "Africa" "Nauru" = "Australia/Oceania" "Nepal" = "Asia" "Netherlands" = "Europe" "New Zealand" = "Australia/Oceania" "Nicaragua" = "North America" "Niger" = "Africa" "Nigeria" = "Africa" "North Korea" = "Asia" "North Macedonia" = "Europe" "Norway" = "Europe" "Oman" = "Asia" "Pakistan" = "Asia" "Palau" = "Australia/Oceania" "Panama" = "North America" "Papua New Guinea" = "Australia/Oceania" "Paraguay" = "South America" "Peru" = "South America" "Philippines" = "Asia" "Poland" = "Europe" "Portugal" = "Europe" "Qatar" = "Asia" "Romania" = "Europe" "Russia" = "Asia" "Rwanda" = "Africa" "Saint Kitts and Nevis" = "North America" "Saint Lucia" = "North America" "Saint Vincent and the Grenadines" = "North America" "Samoa" = "Australia/Oceania" "San Marino" = "Europe" "Sao Tome and Principe" = "Africa" "Saudi Arabia" = "Asia" "Senegal" = "Africa" "Serbia" = "Europe" "Seychelles" = "Africa" "Sierra Leone" = "Africa" "Singapore" = "Asia" "Slovakia" = "Europe" "Slovenia" = "Europe" "Solomon Islands" = "Australia/Oceania" "Somalia" = "Africa" "South Africa" = "Africa" "South Korea" = "Asia" "South Sudan" = "Africa" "Spain" = "Europe" "Sri Lanka" = "Asia" "Sudan" = "Africa" "Suriname" = "South America" "Sweden" = "Europe" "Switzerland" = "Europe" "Syria" = "Asia" "Taiwan" = "Asia" "Tajikistan" = "Asia" "Tanzania" = "Africa" "Thailand" = "Asia" "Timor-Leste" = "Asia" "Togo" = "Africa" "Tonga" = "Australia/Oceania" "Trinidad and Tobago" = "North America" "Tunisia" = "Africa" "Turkey" = "Asia" "Turkmenistan" = "Asia" "Tuvalu" = "Australia/Oceania" "Uganda" = "Africa" "Ukraine" = "Europe" "United Arab Emirates" = "Asia" "United Kingdom" = "Europe" "United States of America" = "North America" "Uruguay" = "South America" "Uzbekistan" = "Asia" "Vanuatu" = "Australia/Oceania" "Vatican City (Holy See)" = "Europe" "Venezuela" = "South America" "Vietnam" = "Asia" "Yemen" = "Asia" "Zambia" = "Africa" "Zimbabwe" = "Africa" } if ($PSBoundParameters.ContainsKey('Country')) { if ($CountryToContinent[$Country]) { $CountryToContinent[$Country] } else { "Unknown" } } else { $CountryToContinent } } function Convert-CountryToCountryCode { <# .SYNOPSIS Converts a country name to a country code, or when used with a switch to full culture information .DESCRIPTION Converts a country name to a country code, or when used with a switch to full culture information .PARAMETER CountryName Country name in it's english name .PARAMETER All Provide full culture information rather than just the country code .EXAMPLE Convert-CountryToCountryCode -CountryName 'Poland' .EXAMPLE Convert-CountryToCountryCode -CountryName 'Poland' -All .EXAMPLE $Test = Convert-CountryToCountryCode $Test['India']['Culture'] $Test['India']['RegionInformation'] .EXAMPLE $Test = Convert-CountryToCountryCode $Test['Poland']['Culture'] $Test['Poland']['RegionInformation'] .EXAMPLE Convert-CountryToCountryCode -CountryName 'Polska' Convert-CountryToCountryCode -CountryName 'Poland' Convert-CountryToCountryCode -CountryName 'CZECH REPUBLIC' Convert-CountryToCountryCode -CountryName 'USA' .NOTES General notes #> [cmdletBinding()] param( [string] $CountryName, [switch] $All ) if ($Script:QuickSearchCountries) { if ($CountryName) { if ($All) { $Script:QuickSearchCountries[$CountryName] } else { if ($Script:QuickSearchCountries[$CountryName]) { $Script:QuickSearchCountries[$CountryName].RegionInformation.TwoLetterISORegionName.ToUpper() } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Country $CountryName not found" } else { Write-Warning -Message "Convert-CountryToCountryCode - Country $CountryName name not found" } } } } else { $Script:QuickSearchCountries } } else { $AllCultures = [cultureinfo]::GetCultures([System.Globalization.CultureTypes]::SpecificCultures) $Script:QuickSearchCountries = [ordered] @{ 'Czech Republic' = @{ 'Culture' = [cultureinfo] 'CZ' 'RegionInformation' = [System.Globalization.RegionInfo] 'CZ' } 'Korea, REPUBLIC OF' = @{ 'Culture' = [cultureinfo] 'KR' 'RegionInformation' = [System.Globalization.RegionInfo] 'KR' } 'VIET NAM' = @{ 'Culture' = [cultureinfo] 'VN' 'RegionInformation' = [System.Globalization.RegionInfo] 'VN' } } foreach ($Culture in $AllCultures) { $RegionInformation = [System.Globalization.RegionInfo]::new($Culture) $Script:QuickSearchCountries[$RegionInformation.EnglishName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } $Script:QuickSearchCountries[$RegionInformation.DisplayName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } $Script:QuickSearchCountries[$RegionInformation.NativeName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } $Script:QuickSearchCountries[$RegionInformation.ThreeLetterISORegionName] = @{ 'Culture' = $Culture 'RegionInformation' = $RegionInformation } } if ($CountryName) { if ($All) { $Script:QuickSearchCountries[$CountryName] } else { if ($Script:QuickSearchCountries[$CountryName]) { $Script:QuickSearchCountries[$CountryName].RegionInformation.TwoLetterISORegionName.ToUpper() } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw "Country $CountryName not found" } else { Write-Warning -Message "Convert-CountryToCountryCode - Country $CountryName name not found" } } } } else { $Script:QuickSearchCountries } } } Function Convert-DomainFqdnToNetBIOS { <# .SYNOPSIS Converts FQDN to NetBIOS name for Active Directory Domain .DESCRIPTION Converts FQDN to NetBIOS name for Active Directory Domain .PARAMETER DomainName DomainName for current forest or trusted forest .EXAMPLE Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.xyz' .EXAMPLE Convert-DomainFqdnToNetBIOS -Domain 'ad.evotec.pl' .NOTES General notes #> [cmdletBinding()] param ( [string] $DomainName ) if (-not $Script:CacheFQDN) { $Script:CacheFQDN = @{} } if ($Script:CacheFQDN[$DomainName]) { $Script:CacheFQDN[$DomainName] } else { $objRootDSE = [System.DirectoryServices.DirectoryEntry] "LDAP://$DomainName/RootDSE" $ConfigurationNC = $objRootDSE.configurationNamingContext $Searcher = [System.DirectoryServices.DirectorySearcher] @{ SearchScope = "subtree" SearchRoot = "LDAP://cn=Partitions,$ConfigurationNC" Filter = "(&(objectcategory=Crossref)(dnsRoot=$DomainName)(netbiosname=*))" } $null = $Searcher.PropertiesToLoad.Add("netbiosname") $Script:CacheFQDN[$DomainName] = ($Searcher.FindOne()).Properties.Item("netbiosname") $Script:CacheFQDN[$DomainName] } } function Convert-DomainToSid { <# .SYNOPSIS Converts Domain Name to SID .DESCRIPTION Converts Domain Name to SID .PARAMETER DomainName DomainName for current forest or trusted forest .EXAMPLE Convert-DomainToSid -DomainName 'test.evotec.pl' .NOTES General notes #> [cmdletBinding()] param( [parameter(Mandatory)][string] $DomainName ) try { $BinarySID = ([ADSI]"LDAP://$DomainName").objectsid $DomainSidValue = [System.Security.Principal.SecurityIdentifier]::new($BinarySID.Value, 0).Value $DomainSidValue } catch { Write-Warning -Message "Convert-DomainToSid - Failed conversion with error $($_.Exception.Message)" } } function Convert-ExchangeEmail { <# .SYNOPSIS Converts a list of Exchange email addresses into a readable and exportable format. .DESCRIPTION This function takes a list of Exchange email addresses and processes them to make them more readable and suitable for export. .PARAMETER Emails List of email addresses in Exchange or Exchange Online format, also known as proxy addresses. .PARAMETER Separator The separator to use between each processed email address. Default is ', '. .PARAMETER RemoveDuplicates Switch to remove duplicate email addresses from the list. .PARAMETER RemovePrefix Switch to remove any prefixes like 'SMTP:', 'SIP:', 'spo:', etc. from the email addresses. .PARAMETER AddSeparator Switch to join the processed email addresses using the specified separator. .EXAMPLE $Emails = @() $Emails += 'SIP:test@email.com' $Emails += 'SMTP:elo@maiu.com' $Emails += 'sip:elo@maiu.com' $Emails += 'Spo:dfte@sdsd.com' $Emails += 'SPO:myothertest@sco.com' Convert-ExchangeEmail -Emails $Emails -RemovePrefix -RemoveDuplicates -AddSeparator #> #> [CmdletBinding()] param( [string[]] $Emails, [string] $Separator = ', ', [switch] $RemoveDuplicates, [switch] $RemovePrefix, [switch] $AddSeparator ) if ($RemovePrefix) { $Emails = $Emails -replace 'smtp:', '' -replace 'sip:', '' -replace 'spo:', '' } if ($RemoveDuplicates) { $Emails = $Emails | Sort-Object -Unique } if ($AddSeparator) { $Emails = $Emails -join $Separator } return $Emails } function Convert-ExchangeItems { <# .SYNOPSIS Converts the count of Exchange items to a specified default value if the count is null. .DESCRIPTION This function takes the count of Exchange items and returns the count if it is not null. If the count is null, it returns the specified default value. .PARAMETER Count The count of Exchange items to be processed. .PARAMETER Default The default value to return if the count is null. Default is 'N/A'. .EXAMPLE Convert-ExchangeItems -Count 10 -Default 'No items' # Returns 10 .EXAMPLE Convert-ExchangeItems -Count $null -Default 'No items' # Returns 'No items' .NOTES General notes #> [cmdletbinding()] param( [int] $Count, [string] $Default = 'N/A' ) if ($null -eq $Count) { return $Default } else { return $Count } } function Convert-ExchangeRecipient { <# .SYNOPSIS Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name .DESCRIPTION Convert msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails to their respective name .PARAMETER RecipientTypeDetails RecipientTypeDetails to convert .PARAMETER RecipientType RecipientType to convert .PARAMETER RemoteRecipientType Parameter description .EXAMPLE $Users = Get-ADUser -Filter * -Properties Mail, ProxyAddresses, msExchRemoteRecipientType, msExchRecipientDisplayType, msExchRecipientTypeDetails, MailNickName $UsersModified = foreach ($User in $Users) { [PSCUstomObject] @{ Name = $User.Name Mail = $User.Mail MailNickName = $User.MailNickName msExchRemoteRecipientType = Convert-ExchangeRecipient -msExchRemoteRecipientType $User.msExchRemoteRecipientType msExchRecipientDisplayType = Convert-ExchangeRecipient -msExchRecipientDisplayType $User.msExchRecipientDisplayType msExchRecipientTypeDetails = Convert-ExchangeRecipient -msExchRecipientTypeDetails $User.msExchRecipientTypeDetails ProxyAddresses = Convert-ExchangeEmail -AddSeparator -RemovePrefix -RemoveDuplicates -Separator ',' -Emails $User.ProxyAddresses } } $UsersModified | Out-HtmlView -Filtering -ScrollX .EXAMPLE Convert-ExchangeRecipient -msExchRemoteRecipientType 17 Convert-ExchangeRecipient -msExchRecipientDisplayType 17 Convert-ExchangeRecipient -msExchRecipientTypeDetails 17 .NOTES Based on: - https://granikos.eu/exchange-recipient-type-values/ - https://answers.microsoft.com/en-us/msoffice/forum/all/recipient-type-values/7c2620e5-9870-48ba-b5c2-7772c739c651 - https://www.undocumented-features.com/2020/05/06/every-last-msexchrecipientdisplaytype-and-msexchrecipienttypedetails-value/ #> [alias('Convert-ExchangeRecipientDetails')] [cmdletbinding(DefaultParameterSetName = 'msExchRecipientTypeDetails')] param( [parameter(ParameterSetName = 'msExchRecipientTypeDetails')][alias('RecipientTypeDetails')][string] $msExchRecipientTypeDetails, [parameter(ParameterSetName = 'msExchRecipientDisplayType')][alias('RecipientType')][string] $msExchRecipientDisplayType, [parameter(ParameterSetName = 'msExchRemoteRecipientType')][alias('RemoteRecipientType')][string] $msExchRemoteRecipientType, [parameter(ParameterSetName = 'msExchRecipientTypeDetails')] [parameter(ParameterSetName = 'msExchRecipientDisplayType')] [parameter(ParameterSetName = 'msExchRemoteRecipientType')] [switch] $All ) if ($PSBoundParameters.ContainsKey('msExchRecipientTypeDetails')) { $ListMsExchRecipientTypeDetails = [ordered] @{ '0' = 'None' '1' = 'UserMailbox' '2' = 'LinkedMailbox' '4' = 'SharedMailbox' '8' = 'LegacyMailbox' '16' = 'RoomMailbox' '32' = 'EquipmentMailbox' '64' = 'MailContact' '128' = 'MailUser' '256' = 'MailUniversalDistributionGroup' '512' = 'MailNonUniversalGroup' '1024' = 'MailUniversalSecurityGroup' '2048' = 'DynamicDistributionGroup' '4096' = 'PublicFolder' '8192' = 'SystemAttendantMailbox' '16384' = 'SystemMailbox' '32768' = 'MailForestContact' '65536' = 'User' '131072' = 'Contact' '262144' = 'UniversalDistributionGroup' '524288' = 'UniversalSecurityGroup' '1048576' = 'NonUniversalGroup' '2097152' = 'Disable User' '4194304' = 'MicrosoftExchange' '8388608' = 'ArbitrationMailbox' '16777216' = 'MailboxPlan' '33554432' = 'LinkedUser' '268435456' = 'RoomList' '536870912' = 'DiscoveryMailbox' '1073741824' = 'RoleGroup' '2147483648' = 'RemoteUserMailbox' '4294967296' = 'Computer' '8589934592' = 'RemoteRoomMailbox' '17179869184' = 'RemoteEquipmentMailbox' '34359738368' = 'RemoteSharedMailbox' '68719476736' = 'PublicFolderMailbox' '137438953472' = 'Team Mailbox' '274877906944' = 'RemoteTeamMailbox' '549755813888' = 'MonitoringMailbox' '1099511627776' = 'GroupMailbox' '2199023255552' = 'LinkedRoomMailbox' '4398046511104' = 'AuditLogMailbox' '8796093022208' = 'RemoteGroupMailbox' '17592186044416' = 'SchedulingMailbox' '35184372088832' = 'GuestMailUser' '70368744177664' = 'AuxAuditLogMailbox' '140737488355328' = 'SupervisoryReviewPolicyMailbox' } if ($All) { $ListMsExchRecipientTypeDetails } else { if ($null -ne $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails]) { $ListMsExchRecipientTypeDetails[$msExchRecipientTypeDetails] } else { $msExchRecipientTypeDetails } } } elseif ($PSBoundParameters.ContainsKey('msExchRecipientDisplayType')) { $ListMsExchRecipientDisplayType = [ordered] @{ '0' = 'MailboxUser' '1' = 'DistributionGroup' '2' = 'PublicFolder' '3' = 'DynamicDistributionGroup' '4' = 'Organization' '5' = 'PrivateDistributionList' '6' = 'RemoteMailUser' '7' = 'ConferenceRoomMailbox' '8' = 'EquipmentMailbox' '10' = 'ArbitrationMailbox' '11' = 'MailboxPlan' '12' = 'LinkedUser' '15' = 'RoomList' '17' = 'Microsoft365Group' '-2147483642' = 'SyncedMailboxUser' '-2147483391' = 'SyncedUDGasUDG' '-2147483386' = 'SyncedUDGasContact' '-2147483130' = 'SyncedPublicFolder' '-2147482874' = 'SyncedDynamicDistributionGroup' '-2147482106' = 'SyncedRemoteMailUser' '-2147481850' = 'SyncedConferenceRoomMailbox' '-2147481594' = 'SyncedEquipmentMailbox' '-2147481343' = 'SyncedUSGasUDG' '-2147481338' = 'SyncedUSGasContact' '-1073741818' = 'ACLableSyncedMailboxUser' '-1073740282' = 'ACLableSyncedRemoteMailUser' '-1073739514' = 'ACLableSyncedUSGasContact' '-1073739511' = 'SyncedUSGasUSG' '1043741833' = 'SecurityDistributionGroup' '1073739511' = 'SyncedUSGasUSG' '1073739514' = 'ACLableSyncedUSGasContact' '1073741824' = 'ACLableMailboxUser' '1073741830' = 'ACLableRemoteMailUser' } if ($All) { $ListMsExchRecipientDisplayType } else { if ($null -ne $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType]) { $ListMsExchRecipientDisplayType[$msExchRecipientDisplayType] } else { $msExchRecipientDisplayType } } } elseif ($PSBoundParameters.ContainsKey('msExchRemoteRecipientType')) { $ListMsExchRemoteRecipientType = [ordered] @{ '1' = 'ProvisionMailbox' '2' = 'ProvisionArchive (On-Prem Mailbox)' '3' = 'ProvisionMailbox, ProvisionArchive' '4' = 'Migrated (UserMailbox)' '6' = 'ProvisionArchive, Migrated' '8' = 'DeprovisionMailbox' '10' = 'ProvisionArchive, DeprovisionMailbox' '16' = 'DeprovisionArchive (On-Prem Mailbox)' '17' = 'ProvisionMailbox, DeprovisionArchive' '20' = 'Migrated, DeprovisionArchive' '24' = 'DeprovisionMailbox, DeprovisionArchive' '33' = 'ProvisionMailbox, RoomMailbox' '35' = 'ProvisionMailbox, ProvisionArchive, RoomMailbox' '36' = 'Migrated, RoomMailbox' '38' = 'ProvisionArchive, Migrated, RoomMailbox' '49' = 'ProvisionMailbox, DeprovisionArchive, RoomMailbox' '52' = 'Migrated, DeprovisionArchive, RoomMailbox' '65' = 'ProvisionMailbox, EquipmentMailbox' '67' = 'ProvisionMailbox, ProvisionArchive, EquipmentMailbox' '68' = 'Migrated, EquipmentMailbox' '70' = 'ProvisionArchive, Migrated, EquipmentMailbox' '81' = 'ProvisionMailbox, DeprovisionArchive, EquipmentMailbox' '84' = 'Migrated, DeprovisionArchive, EquipmentMailbox' '100' = 'Migrated, SharedMailbox' '102' = 'ProvisionArchive, Migrated, SharedMailbox' '116' = 'Migrated, DeprovisionArchive, SharedMailbox' } if ($All) { $ListMsExchRemoteRecipientType } else { if ($null -ne $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType]) { $ListMsExchRemoteRecipientType[$msExchRemoteRecipientType] } else { $msExchRemoteRecipientType } } } } function Convert-ExchangeSize { <# .SYNOPSIS Converts the size of Exchange data to a specified unit of measurement. .DESCRIPTION This function takes the size of Exchange data and converts it to the specified unit of measurement (Bytes, KB, MB, GB, TB). .PARAMETER To The unit of measurement to convert the size to. Default is 'MB'. .PARAMETER Size The size of Exchange data to be converted. .PARAMETER Precision The number of decimal places to round the converted size to. Default is 4. .PARAMETER Display Switch to display the converted size with the unit of measurement. .PARAMETER Default The default value to return if the size is null or empty. Default is 'N/A'. .EXAMPLE Convert-ExchangeSize -To MB -Size '49 GB (52,613,349,376 bytes)' # Returns the size converted to MB. .EXAMPLE Convert-ExchangeSize -To GB -Size '49 GB (52,613,349,376 bytes)' -Precision 2 -Display # Returns the size converted to GB with 2 decimal places and displays the result. #> [cmdletbinding()] param( [validateset("Bytes", "KB", "MB", "GB", "TB")][string]$To = 'MB', [string]$Size, [int]$Precision = 4, [switch]$Display, [string]$Default = 'N/A' ) if ([string]::IsNullOrWhiteSpace($Size)) { return $Default } $Pattern = [Regex]::new('(?<=\()([0-9]*[,.].*[0-9])') $Value = ($Size | Select-String $Pattern -AllMatches).Matches.Value if ($null -ne $Value) { $Value = $Value.Replace(',', '').Replace('.', '') } switch ($To) { "Bytes" { return $value } "KB" { $Value = $Value / 1KB } "MB" { $Value = $Value / 1MB } "GB" { $Value = $Value / 1GB } "TB" { $Value = $Value / 1TB } } if ($Display) { return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To" } else { return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero) } } function ConvertFrom-Color { <# .SYNOPSIS Converts color names or hex codes to different formats. .DESCRIPTION ConvertFrom-Color function converts color names or hex codes to different formats such as decimal values or System.Drawing.Color objects. .PARAMETER Color Specifies the color names or hex codes to convert. .PARAMETER AsDecimal Indicates whether to convert the color to a decimal value. .PARAMETER AsDrawingColor Indicates whether to convert the color to a System.Drawing.Color object. .EXAMPLE ConvertFrom-Color -Color Red, Blue -AsDecimal Converts the colors Red and Blue to decimal values. .EXAMPLE ConvertFrom-Color -Color "#FFA500" -AsDrawingColor Converts the color with hex code #FFA500 to a System.Drawing.Color object. #> [alias('Convert-FromColor')] [CmdletBinding()] param ( [ValidateScript( { if ($($_ -in $Script:RGBColors.Keys -or $_ -match "^#([A-Fa-f0-9]{6})$" -or $_ -eq "") -eq $false) { throw "The Input value is not a valid colorname nor an valid color hex code." } else { $true } })] [alias('Colors')][string[]] $Color, [switch] $AsDecimal, [switch] $AsDrawingColor ) $Colors = foreach ($C in $Color) { $Value = $Script:RGBColors."$C" if ($C -match "^#([A-Fa-f0-9]{6})$") { $C continue } if ($null -eq $Value) { continue } $HexValue = Convert-Color -RGB $Value Write-Verbose "Convert-FromColor - Color Name: $C Value: $Value HexValue: $HexValue" if ($AsDecimal) { [Convert]::ToInt64($HexValue, 16) } elseif ($AsDrawingColor) { [System.Drawing.Color]::FromArgb("#$($HexValue)") } else { "#$($HexValue)" } } $Colors } $ScriptBlockColors = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:RGBColors.Keys | Where-Object { $_ -like "$wordToComplete*" } } Register-ArgumentCompleter -CommandName ConvertFrom-Color -ParameterName Color -ScriptBlock $ScriptBlockColors function Convert-HexToBinary { <# .SYNOPSIS Converts a hexadecimal string to a binary representation. .DESCRIPTION This function takes a hexadecimal string as input and converts it to a binary representation. .PARAMETER Hex Specifies the hexadecimal string to convert to binary. .EXAMPLE Convert-HexToBinary -Hex "1A" # Outputs: 00011010 .EXAMPLE "1A" | Convert-HexToBinary # Outputs: 00011010 #> [CmdletBinding()] param( [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)] [string] $Hex ) $return = for ($i = 0; $i -lt $Hex.Length ; $i += 2) { [Byte]::Parse($Hex.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber) } Write-Output $return -NoEnumerate } function Convert-Identity { <# .SYNOPSIS Small command that tries to resolve any given object .DESCRIPTION Small command that tries to resolve any given object - be it SID, DN, FSP or Netbiosname .PARAMETER Identity Type to resolve in form of Identity, DN, SID .PARAMETER SID Allows to pass SID directly, rather then going thru verification process .PARAMETER Name Allows to pass Name directly, rather then going thru verification process .PARAMETER Force Allows to clear cache, useful when you want to force refresh .EXAMPLE $Identity = @( 'S-1-5-4' 'S-1-5-4' 'S-1-5-11' 'S-1-5-32-549' 'S-1-5-32-550' 'S-1-5-32-548' 'S-1-5-64-10' 'S-1-5-64-14' 'S-1-5-64-21' 'S-1-5-18' 'S-1-5-19' 'S-1-5-32-544' 'S-1-5-20-20-10-51' # Wrong SID 'S-1-5-21-853615985-2870445339-3163598659-512' 'S-1-5-21-3661168273-3802070955-2987026695-512' 'S-1-5-21-1928204107-2710010574-1926425344-512' 'CN=Test Test 2,OU=Users,OU=Production,DC=ad,DC=evotec,DC=pl' 'Test Local Group' 'przemyslaw.klys@evotec.pl' 'test2' 'NT AUTHORITY\NETWORK' 'NT AUTHORITY\SYSTEM' 'S-1-5-21-853615985-2870445339-3163598659-519' 'TEST\some' 'EVOTECPL\Domain Admins' 'NT AUTHORITY\INTERACTIVE' 'INTERACTIVE' 'EVOTEC\Domain Admins' 'EVOTECPL\Domain Admins' 'Test\Domain Admins' 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # Valid 'CN=S-1-5-21-1928204107-2710010574-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # not valid 'CN=S-1-5-21-1928204107-2710010574-1926425344-512,CN=ForeignSecurityPrincipals,DC=ad,DC=evotec,DC=xyz' # cached ) $TestOutput = Convert-Identity -Identity $Identity -Verbose Output: Name SID DomainName Type Error ---- --- ---------- ---- ----- NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\Authenticated Users S-1-5-11 WellKnownGroup BUILTIN\Server Operators S-1-5-32-549 WellKnownGroup BUILTIN\Print Operators S-1-5-32-550 WellKnownGroup BUILTIN\Account Operators S-1-5-32-548 WellKnownGroup NT AUTHORITY\NTLM Authentication S-1-5-64-10 WellKnownGroup NT AUTHORITY\SChannel Authentication S-1-5-64-14 WellKnownGroup NT AUTHORITY\Digest Authentication S-1-5-64-21 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative NT AUTHORITY\NETWORK SERVICE S-1-5-19 WellKnownGroup BUILTIN\Administrators S-1-5-32-544 WellKnownAdministrative S-1-5-20-20-10-51 S-1-5-20-20-10-51 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative EVOTECPL\TestingAD S-1-5-21-3661168273-3802070955-2987026695-1111 ad.evotec.pl NotAdministrative EVOTEC\Test Local Group S-1-5-21-853615985-2870445339-3163598659-3610 ad.evotec.xyz NotAdministrative EVOTEC\przemyslaw.klys S-1-5-21-853615985-2870445339-3163598659-1105 ad.evotec.xyz NotAdministrative test2 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." NT AUTHORITY\NETWORK S-1-5-2 WellKnownGroup NT AUTHORITY\SYSTEM S-1-5-18 WellKnownAdministrative EVOTEC\Enterprise Admins S-1-5-21-853615985-2870445339-3163598659-519 ad.evotec.xyz Administrative TEST\some S-1-5-21-1928204107-2710010574-1926425344-1106 test.evotec.pl NotAdministrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup NT AUTHORITY\INTERACTIVE S-1-5-4 WellKnownGroup EVOTEC\Domain Admins S-1-5-21-853615985-2870445339-3163598659-512 ad.evotec.xyz Administrative EVOTECPL\Domain Admins S-1-5-21-3661168273-3802070955-2987026695-512 ad.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative S-1-5-21-1928204107-2710010574-512 S-1-5-21-1928204107-2710010574-512 Unknown Exception calling "Translate" with "1" argument(s): "Some or all identity references could not be translated." TEST\Domain Admins S-1-5-21-1928204107-2710010574-1926425344-512 test.evotec.pl Administrative .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Identity')] param( [parameter(ParameterSetName = 'Identity', Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)][string[]] $Identity, [parameter(ParameterSetName = 'SID', Mandatory)][System.Security.Principal.SecurityIdentifier[]] $SID, [parameter(ParameterSetName = 'Name', Mandatory)][string[]] $Name, [switch] $Force ) Begin { if (-not $Script:GlobalCacheSidConvert -or $Force) { $Script:GlobalCacheSidConvert = @{ 'NT AUTHORITY\SYSTEM' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'BUILTIN\Users' = [PSCustomObject] @{ Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Guests' = [PSCustomObject] @{ Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Power Users' = [PSCustomObject] @{ Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Account Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Server Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Print Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Backup Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Replicator' = [PSCustomObject] @{ Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Pre-Windows 2000 Compatible Access' = [PSCustomObject] @{ Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Desktop Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Network Configuration Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Incoming Forest Trust Builders' = [PSCustomObject] @{ Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Monitor Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Performance Log Users' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Windows Authorization Access Group' = [PSCustomObject] @{ Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Terminal Server License Servers' = [PSCustomObject] @{ Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Distributed COM Users' = [PSCustomObject] @{ Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\IIS_IUSRS' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Cryptographic Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Event Log Readers' = [PSCustomObject] @{ Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Certificate Service DCOM Access' = [PSCustomObject] @{ Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Remote Access Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Endpoint Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\RDS Management Servers' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Hyper-V Administrators' = [PSCustomObject] @{ Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Access Control Assistance Operators' = [PSCustomObject] @{ Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'BUILTIN\Remote Management Users' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'Window Manager\Window Manager Group' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\WdiServiceHost' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\MSSQLSERVER' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLSERVERAGENT' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\SQLTELEMETRY' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT SERVICE\ADSync' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'NT Service\himds' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' DomainName = '' Type = 'WellKnownGroup' Error = '' } } } } Process { if ($Identity) { foreach ($Ident in $Identity) { $MatchRegex = [Regex]::Matches($Ident, "S-\d-\d+-(\d+-|){1,14}\d+") if ($Script:GlobalCacheSidConvert[$Ident]) { Write-Verbose "Convert-Identity - Processing $Ident (Cache)" $Script:GlobalCacheSidConvert[$Ident] } elseif ($MatchRegex.Success) { Write-Verbose "Convert-Identity - Processing $Ident (SID)" if ($MatchRegex.Value -ne $Ident) { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $MatchRegex.Value } else { $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $Ident } $Script:GlobalCacheSidConvert[$Ident] } elseif ($Ident -like '*DC=*') { Write-Verbose "Convert-Identity - Processing $Ident (DistinguishedName)" try { $Object = [adsi]"LDAP://$($Ident)" $SIDValue = [System.Security.Principal.SecurityIdentifier]::new($Object.objectSid.Value, 0).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{ Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } else { Write-Verbose "Convert-Identity - Processing $Ident (Other)" try { $SIDValue = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] = ConvertFrom-SID -SID $SIDValue } catch { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{ Name = $Ident SID = $null DomainName = '' Type = 'Unknown' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' } } $Script:GlobalCacheSidConvert[$Ident] } } } else { if ($SID) { foreach ($S in $SID) { if ($Script:GlobalCacheSidConvert[$S]) { $Script:GlobalCacheSidConvert[$S] } else { $Script:GlobalCacheSidConvert[$S] = ConvertFrom-SID -SID $S $Script:GlobalCacheSidConvert[$S] } } } else { foreach ($Ident in $Name) { if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else { $Script:GlobalCacheSidConvert[$Ident] = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value $Script:GlobalCacheSidConvert[$Ident] } } } } } End { } } function Convert-IpAddressToPtr { <# .SYNOPSIS Converts an IP address to a PTR record .DESCRIPTION This function takes an IP address as input and converts it to a PTR record. .PARAMETER IPAddress The IP address to convert .PARAMETER AsObject Should the function return an object instead of a string? .EXAMPLE Convert-IpAddressToPtr -IPAddress "10.1.2.3" .EXAMPLE Convert-IpAddressToPtr -IPAddress "10.1.2.3", "8.8.8.8" -AsObject .NOTES General notes #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string[]]$IPAddress, [switch] $AsObject ) foreach ($IP in $IPAddress) { $octets = $IP -split "\." [array]::Reverse($octets) $ptrString = ($octets -join ".") + ".in-addr.arpa" if ($AsObject) { [pscustomobject] @{ IPAddress = $IP PTR = $ptrString } } else { $ptrString } } } function Convert-KeyToKeyValue { <# .SYNOPSIS Converts keys of an object to key-value pairs. .DESCRIPTION This function takes an object and converts its keys to key-value pairs, where the key is the original key concatenated with its corresponding value. .PARAMETER Object Specifies the object whose keys are to be converted to key-value pairs. .EXAMPLE $Object = @{ Key1 = 'Value1' Key2 = 'Value2' } Convert-KeyToKeyValue -Object $Object # Returns a new hash table with keys as 'Key1 (Value1)' and 'Key2 (Value2)'. #> [CmdletBinding()] param ( [object] $Object ) $NewHash = [ordered] @{} foreach ($O in $Object.Keys) { $KeyName = "$O ($($Object.$O))" $KeyValue = $Object.$O $NewHash.$KeyName = $KeyValue } return $NewHash } function Convert-Office365License { <# .SYNOPSIS Converts Office 365 licenses between their names and SKUs. .DESCRIPTION This function allows for the conversion of Office 365 licenses between their names and SKUs. It provides flexibility to handle multiple values for licenses. .PARAMETER License Specifies the Office 365 license SKU or name to convert. Supports multiple values. .PARAMETER ToSku Indicates whether to convert the license name to SKU. .PARAMETER Separator Specifies the separator to use when returning multiple values. .PARAMETER ReturnArray Indicates whether to return the result as an array. .EXAMPLE Convert-Office365License -License 'VISIOCLIENT','PROJECTONLINE_PLAN_1','test','tenant:VISIOCLIENT' Converts the specified licenses to their corresponding SKUs. .EXAMPLE Convert-Office365License -License "Office 365 A3 for faculty", "Project Plan 3 (for Department)", 'test' -ToSku Converts the specified license names to their corresponding SKUs. .NOTES For more information on Office 365 licensing, refer to: https://learn.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference #> [CmdletBinding()] param( [Parameter(Position = 0, ValueFromPipeline)][Array] $License, [alias('SKU')][switch] $ToSku, [string] $Separator = ', ', [switch] $ReturnArray ) Begin { $O365SKU = [ordered] @{ 'AAD_BASIC' = "Azure Active Directory Basic" 'AAD_BASIC_EDU' = "Azure Active Directory Basic for Education" 'AAD_EDU' = "Azure Active Directory for Education" 'AAD_PREMIUM' = "Azure Active Directory Premium P1" 'AAD_PREMIUM_FACULTY' = "Azure Active Directory Premium P1 for Faculty" 'AAD_PREMIUM_P2' = "Azure Active Directory Premium P2" 'AAD_SMB' = "Azure Active Directory" 'ADALLOM_FOR_AATP' = "SecOps Investigation for MDI" 'ADALLOM_O365' = "Office 365 Cloud App Security" 'ADALLOM_S_DISCOVERY' = "CLOUD APP SECURITY DISCOVERY" 'ADALLOM_S_O365' = "Office 365 Cloud App Security" 'ADALLOM_S_STANDALONE' = "MICROSOFT CLOUD APP SECURITY" 'ADALLOM_S_STANDALONE_DOD' = "Microsoft Defender for Cloud Apps for DOD" 'ADALLOM_STANDALONE' = "Microsoft Cloud App Security" 'ADV_COMMS' = "Advanced Communications" 'ATA' = "Microsoft Defender for Identity" 'ATP_ENTERPRISE' = "Microsoft Defender for Office 365 (Plan 1)" 'ATP_ENTERPRISE_FACULTY' = "Microsoft Defender for Office 365 (Plan 1) Faculty" 'ATP_ENTERPRISE_GOV' = "Microsoft Defender for Office 365 (Plan 1) GCC" 'AX7_USER_TRIAL' = "Microsoft Dynamics AX7 User Trial" 'BI_AZURE_P_2_GOV' = "Power BI Pro for Government" 'BI_AZURE_P0' = "Power BI (free)" 'BI_AZURE_P1' = "Microsoft Power BI Reporting and Analytics Plan 1" 'BI_AZURE_P2' = "Power BI Pro" 'BI_AZURE_P3' = "Power BI Premium Per User" 'BPOS_S_DlpAddOn' = "Data Loss Prevention" 'BPOS_S_TODO_1' = "To-Do (Plan 1)" 'BPOS_S_TODO_2' = "To-Do (Plan 2)" 'BPOS_S_TODO_3' = "To-Do (Plan 3)" 'BPOS_S_TODO_FIRSTLINE' = "To-Do (Firstline)" 'CCIBOTS_PRIVPREV_VIRAL' = "Power Virtual Agents Viral Trial" 'CDS_ATTENDED_RPA' = "Common Data Service Attended RPA" 'CDS_CUSTOMER_INSIGHTS' = "Common Data Service for Customer Insights" 'CDS_CUSTOMER_INSIGHTS_BASE' = "Dataverse for Customer Insights�BASE" 'CDS_CUSTOMER_INSIGHTS_TRIAL' = "Common Data Service for Customer Insights Trial" 'CDS_DB_CAPACITY' = "Common Data Service Database Capacity" 'CDS_DB_CAPACITY_GOV' = "Common Data Service Database Capacity for Government" 'CDS_FILE_CAPACITY' = "Common Data Service for Apps File Capacity" 'CDS_Flow_Business_Process' = "Common data service for Flow per business process plan" 'CDS_FORM_PRO_USL' = "Common Data Service" 'CDS_LOG_CAPACITY' = "Common Data Service Log Capacity" 'CDS_O365_E5_KM' = "Common Data Service for SharePoint Syntex" 'CDS_O365_F1' = "Common Data Service for Teams" 'CDS_O365_F1_GCC' = "Common Data Service for Teams_F1 GCC" 'CDS_O365_P1' = "COMMON DATA SERVICE FOR TEAMS_P1" 'CDS_O365_P1_GCC' = "Common Data Service for Teams_P1 GCC" 'CDS_O365_P2' = "Common Data Service for Teams" 'CDS_O365_P2_GCC' = "COMMON DATA SERVICE FOR TEAMS_P2 GCC" 'CDS_O365_P3' = "Common Data Service for Teams" 'CDS_O365_P3_GCC' = "Common Data Service for Teams" 'CDS_PER_APP' = "CDS PowerApps per app plan" 'CDS_PER_APP_IWTRIAL' = "CDS Per app baseline access" 'CDS_POWERAPPS_PORTALS_LOGIN' = "Common Data Service Power Apps Portals Login Capacity" 'CDS_POWERAPPS_PORTALS_LOGIN_GCC' = "Common Data Service Power Apps Portals Login Capacity for GCC" 'CDS_POWERAPPS_PORTALS_PAGEVIEW' = "CDS PowerApps Portals page view capacity add-on" 'CDS_POWERAPPS_PORTALS_PAGEVIEW_GCC' = "CDS PowerApps Portals page view capacity add-on for GCC" 'CDS_REMOTE_ASSIST' = "Common Data Service for Remote Assist" 'CDS_UNATTENDED_RPA' = "Common Data Service Unattended RPA" 'CDS_VIRTUAL_AGENT_BASE' = "Common Data Service for Virtual Agent Base" 'CDS_VIRTUAL_AGENT_USL' = "Common Data Service" 'CDSAICAPACITY' = "AI Builder Capacity add-on" 'CDSAICAPACITY_PERAPP' = "AI Builder capacity Per App add-on" 'CDSAICAPACITY_PERUSER' = "AI Builder capacity Per User add-on" 'CDSAICAPACITY_PERUSER_NEW' = "AI Builder capacity Per User add-on" 'CMPA_addon' = "Compliance Manager Premium Assessment Add-On" 'CMPA_addon_GCC' = "Compliance Manager Premium Assessment Add-On for GCC" 'COMMUNICATIONS_COMPLIANCE' = "Microsoft Communications Compliance" 'COMMUNICATIONS_DLP' = "Microsoft Communications DLP" 'COMPLIANCE_MANAGER_PREMIUM_ASSESSMENT_ADDON' = "Compliance Manager Premium Assessment Add-On" 'Content_Explorer' = "Information Protection and Governance Analytics - Premium" 'ContentExplorer_Standard' = "Information Protection and Governance Analytics � Standard" 'CORTEX' = "Microsoft Viva Topics" 'CPC_1' = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB" 'CPC_2' = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB" 'CPC_B_1C_2RAM_64GB' = "Windows 365 Business 1 vCPU 2 GB 64 GB" 'CPC_B_2C_4RAM_128GB' = "Windows 365 Business 2 vCPU 4 GB 128 GB" 'CPC_B_2C_4RAM_256GB' = "Windows 365 Business 2 vCPU 4 GB 256 GB" 'CPC_B_2C_4RAM_64GB' = "Windows 365 Business 2 vCPU 4 GB 64 GB" 'CPC_B_2C_8RAM_128GB' = "Windows 365 Business 2 vCPU 8 GB 128 GB" 'CPC_B_2C_8RAM_256GB' = "Windows 365 Business 2 vCPU 8 GB 256 GB" 'CPC_B_4C_16RAM_128GB' = "Windows 365 Business 4 vCPU 16 GB 128 GB" 'CPC_B_4C_16RAM_128GB_WHB' = "Windows 365 Business 4 vCPU 16 GB 128 GB (with Windows Hybrid Benefit)" 'CPC_B_4C_16RAM_256GB' = "Windows 365 Business 4 vCPU 16 GB 256 GB" 'CPC_B_4C_16RAM_512GB' = "Windows 365 Business 4 vCPU 16 GB 512 GB" 'CPC_B_8C_32RAM_128GB' = "Windows 365 Business 8 vCPU 32 GB 128 GB" 'CPC_B_8C_32RAM_256GB' = "Windows 365 Business 8 vCPU 32 GB 256 GB" 'CPC_B_8C_32RAM_512GB' = "Windows 365 Business 8 vCPU 32 GB 512 GB" 'CPC_E_1C_2GB_64GB' = "Windows 365 Enterprise 1 vCPU 2 GB 64 GB" 'CPC_E_2C_4GB_128GB' = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB" 'CPC_E_2C_4GB_256GB' = "Windows 365 Enterprise 2 vCPU 4 GB 256 GB" 'CPC_E_2C_4GB_64GB' = "Windows 365 Enterprise 2 vCPU 4 GB 64 GB" 'CPC_E_2C_8GB_128GB' = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB" 'CPC_E_2C_8GB_256GB' = "Windows 365 Enterprise 2 vCPU 8 GB 256 GB" 'CPC_E_4C_16GB_128GB' = "Windows 365 Enterprise 4 vCPU 16 GB 128 GB" 'CPC_E_4C_16GB_256GB' = "Windows 365 Enterprise 4 vCPU 16 GB 256 GB" 'CPC_E_4C_16GB_512GB' = "Windows 365 Enterprise 4 vCPU 16 GB 512 GB" 'CPC_E_8C_32GB_128GB' = "Windows 365 Enterprise 8 vCPU 32 GB 128 GB" 'CPC_E_8C_32GB_256GB' = "Windows 365 Enterprise 8 vCPU 32 GB 256 GB" 'CPC_E_8C_32GB_512GB' = "Windows 365 Enterprise 8 vCPU 32 GB 512 GB" 'CPC_LVL_1' = "Windows 365 Enterprise 2 vCPU 4 GB 128 GB (Preview)" 'CPC_LVL_2' = "Windows 365 Enterprise 2 vCPU 8 GB 128 GB (Preview)" 'CPC_LVL_3' = "Windows 365 Enterprise 4 vCPU 16 GB 256 GB (Preview)" 'CPC_S_2C_4GB_128GB' = "Windows 365 Shared Use 2 vCPU 4 GB 128 GB" 'CPC_S_2C_4GB_256GB' = "Windows 365 Shared Use 2 vCPU 4 GB 256 GB" 'CPC_S_2C_4GB_64GB' = "Windows 365 Shared Use 2 vCPU 4 GB 64 GB" 'CPC_S_2C_8GB_128GB' = "Windows 365 Shared Use 2 vCPU 8 GB 128 GB" 'CPC_S_2C_8GB_256GB' = "Windows 365 Shared Use 2 vCPU 8 GB 256 GB" 'CPC_S_4C_16GB_128GB' = "Windows 365 Shared Use 4 vCPU 16 GB 128 GB" 'CPC_S_4C_16GB_256GB' = "Windows 365 Shared Use 4 vCPU 16 GB 256 GB" 'CPC_S_4C_16GB_512GB' = "Windows 365 Shared Use 4 vCPU 16 GB 512 GB" 'CPC_S_8C_32GB_128GB' = "Windows 365 Shared Use 8 vCPU 32 GB 128 GB" 'CPC_S_8C_32GB_256GB' = "Windows 365 Shared Use 8 vCPU 32 GB 256 GB" 'CPC_S_8C_32GB_512GB' = "Windows 365 Shared Use 8 vCPU 32 GB 512 GB" 'CPC_SS_2' = "Windows 365 Business 2 vCPU, 8 GB, 128 GB" 'CRM_AUTO_ROUTING_ADDON' = "Dynamics 365 Field Service, Enterprise Edition - Resource Scheduling Optimization" 'CRM_AUTO_ROUTING_ENGINE_ADDON' = "Field Service � Automated Routing Engine Add-On" 'CRM_HYBRIDCONNECTOR' = "Dynamics 365 Hybrid Connector" 'CRM_ONLINE_PORTAL' = "Dynamics 365 Enterprise Edition - Additional Portal (Qualified Offer)" 'CRMINSTANCE' = "Dynamics 365 - Additional Production Instance (Qualified Offer)" 'CRMPLAN2' = "Microsoft Dynamics CRM Online Basic" 'CRMSTANDARD' = "Microsoft Dynamics CRM Online" 'CRMSTORAGE' = "Dynamics 365 - Additional Database Storage (Qualified Offer)" 'CRMTESTINSTANCE' = "Dynamics 365 - Additional Non-Production Instance (Qualified Offer)" 'CUSTOMER_KEY' = "Microsoft Customer Key" 'CUSTOMER_VOICE_ADDON' = "Dynamics Customer Voice Add-On" 'Customer_Voice_Base' = "Dynamics 365 Customer Voice Base Plan" 'Customer_Voice_Customer_Insights' = "Microsoft Dynamics 365 Customer Voice for Customer Insights App" 'CUSTOMER_VOICE_DYN365_VIRAL_TRIAL' = "Customer Voice for Dynamics 365 vTrial" 'D365_AssetforSCM' = "Asset Maintenance Add-in" 'D365_CSI_EMBED_CE' = "Dynamics 365 Customer Service Insights for CE Plan" 'D365_CSI_EMBED_CSEnterprise' = "Dynamics 365 Customer Service Insights for CS Enterprise" 'D365_CUSTOMER_SERVICE_ENT_ATTACH' = "Dynamics 365 for Customer Service Enterprise Attach to Qualifying Dynamics 365 Base Offer A" 'D365_FIELD_SERVICE_ATTACH' = "Dynamics 365 for Field Service Attach to Qualifying Dynamics 365 Base Offer" 'D365_Finance' = "Microsoft Dynamics 365 for Finance" 'D365_IOTFORSCM' = "Iot Intelligence Add-in for D365 Supply Chain Management" 'D365_IOTFORSCM_ADDITIONAL' = "IoT Intelligence Add-in Additional Machines" 'D365_MARKETING_USER' = "Dynamics 365 for Marketing USL" 'D365_ProjectOperations' = "Dynamics 365 Project Operations" 'D365_ProjectOperationsCDS' = "Dynamics 365 Project Operations CDS" 'D365_SALES_ENT_ATTACH' = "Dynamics 365 Sales Enterprise Attach to Qualifying Dynamics 365 Base Offer" 'D365_SALES_PRO' = "Dynamics 365 For Sales Professional" 'D365_SALES_PRO_ATTACH' = "Dynamics 365 Sales Professional Attach to Qualifying Dynamics 365 Base Offer" 'D365_SALES_PRO_IW' = "Dynamics 365 For Sales Professional Trial" 'D365_SALES_PRO_IW_Trial' = "Dynamics 365 for Sales Professional Trial" 'D365_SCM' = "DYNAMICS 365 FOR SUPPLY CHAIN MANAGEMENT" 'DATA_INVESTIGATIONS' = "Microsoft Data Investigations" 'DATAVERSE_FOR_POWERAUTOMATE_DESKTOP' = "Dataverse for PAD" 'DATAVERSE_POWERAPPS_PER_APP_NEW' = "Dataverse for Power Apps per app" 'DDYN365_CDS_DYN_P2' = "COMMON DATA SERVICE" 'DEFENDER_ENDPOINT_P1' = "Microsoft Defender for Endpoint P1" 'DEFENDER_ENDPOINT_P1_EDU' = "Microsoft Defender for Endpoint P1 for EDU" 'Defender_Threat_Intelligence' = "Defender Threat Intelligence" 'Deskless' = "Microsoft StaffHub" 'DESKLESSPACK' = "Office 365 F3" 'DEVELOPERPACK' = "Office 365 E3 Developer" 'DEVELOPERPACK_E5' = "Microsoft 365 E5 Developer (without Windows and Audio Conferencing)" 'DYN365_ ENTERPRISE _RELATIONSHIP_SALES' = "Microsoft Relationship Sales solution" 'DYN365_AI_SERVICE_INSIGHTS' = "Dynamics 365 Customer Service Insights Trial" 'DYN365_ASSETMANAGEMENT' = "Dynamics 365 Asset Management Addl Assets" 'DYN365_BUSCENTRAL_ADD_ENV_ADDON' = "Dynamics 365 Business Central Additional Environment Addon" 'DYN365_BUSCENTRAL_DB_CAPACITY' = "Dynamics 365 Business Central Database Capacity" 'DYN365_BUSCENTRAL_ENVIRONMENT' = "Dynamics 365 Business Central Additional Environment Addon" 'DYN365_BUSCENTRAL_ESSENTIAL' = "Dynamics 365 Business Central Essentials" 'DYN365_BUSCENTRAL_PREMIUM' = "Dynamics 365 Business Central Premium" 'DYN365_BUSCENTRAL_TEAM_MEMBER' = "Dynamics 365 Business Central Team Members" 'DYN365_BUSINESS_MARKETING' = "Dynamics 365 for Marketing Business Edition" 'DYN365_CDS_CCI_BOTS' = "Common Data Service for CCI Bots" 'DYN365_CDS_DEV_VIRAL' = "Common Data Service - DEV VIRAL" 'DYN365_CDS_DYN_APPS' = "Common Data Service" 'DYN365_CDS_FINANCE' = "Common Data Service for Dynamics 365 Finance" 'DYN365_CDS_FOR_PROJECT_P1' = "COMMON DATA SERVICE FOR PROJECT P1" 'DYN365_CDS_FORMS_PRO' = "Common Data Service" 'DYN365_CDS_GUIDES' = "Common Data Service" 'DYN365_CDS_O365_F1' = "Common Data Service" 'DYN365_CDS_O365_F1_GCC' = "Common Data Service - O365 F1" 'DYN365_CDS_O365_P1' = "Common Data Service - O365 P1" 'DYN365_CDS_O365_P1_GCC' = "Common Data Service - O365 P1 GCC" 'DYN365_CDS_O365_P2' = "Common Data Service" 'DYN365_CDS_O365_P2_GCC' = "COMMON DATA SERVICE - O365 P2 GCC" 'DYN365_CDS_O365_P3' = "Common Data Service" 'DYN365_CDS_O365_P3_GCC' = "Common Data Service" 'DYN365_CDS_P1_GOV' = "Common Data Service for Government" 'DYN365_CDS_P2' = "Common Data Service - P2" 'DYN365_CDS_P2_GOV' = "Common Data Service for Government" 'DYN365_CDS_PROJECT' = "Common Data Service for Project" 'DYN365_CDS_SUPPLYCHAINMANAGEMENT' = "COMMON DATA SERVICE FOR DYNAMICS 365 SUPPLY CHAIN MANAGEMENT" 'DYN365_CDS_VIRAL' = "Common Data Service" 'DYN365_CS_CHAT' = "Dynamics 365 for Customer Service Chat" 'DYN365_CS_CHAT_FPA' = "Dynamics 365 Customer Service Chat Application Integration" 'DYN365_CS_ENTERPRISE_VIRAL_TRIAL' = "Dynamics 365 Customer Service Enterprise vTrial" 'DYN365_CS_MESSAGING_TPS' = "Dynamics 365 Customer Service Digital Messaging add-on" 'DYN365_CS_MESSAGING_VIRAL_TRIAL' = "Dynamics 365 Customer Service Digital Messaging vTrial" 'DYN365_CS_VOICE' = "Dynamics 365 for Customer Service Voice Add-in" 'DYN365_CS_VOICE_VIRAL_TRIAL' = "Dynamics 365 Customer Service Voice vTrial" 'DYN365_CUSTOMER_INSIGHTS_ATTACH' = "Dynamics 365 Customer Insights Attach" 'DYN365_CUSTOMER_INSIGHTS_BASE' = "Dynamics 365 Customer Insights Standalone" 'DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE' = "Dynamics 365 Customer Insights Engagement Insights" 'DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE_TRIAL' = "Dynamics 365 Customer Insights Engagement Insights Viral" 'DYN365_CUSTOMER_INSIGHTS_VIRAL' = "Dynamics 365 Customer Insights vTrial" 'DYN365_CUSTOMER_SERVICE_PRO' = "Dynamics 365 Customer Service Professional" 'DYN365_CUSTOMER_VOICE_ADDON' = "Dynamics 365 Customer Voice Additional Responses" 'DYN365_CUSTOMER_VOICE_BASE' = "Dynamics 365 Customer Voice" 'DYN365_ENTERPRISE_CASE_MANAGEMENT' = "Dynamics 365 for Case Management Enterprise Edition" 'DYN365_ENTERPRISE_CUSTOMER_SERVICE' = "Dynamics 365 for Customer Service Enterprise Edition" 'DYN365_ENTERPRISE_FIELD_SERVICE' = "Dynamics 365 for Field Service Enterprise Edition" 'DYN365_ENTERPRISE_P1' = "Dynamics 365 P1" 'DYN365_ENTERPRISE_P1_IW' = "Dynamics 365 P1 Tria for Information Workers" 'DYN365_ENTERPRISE_PLAN1' = "Dynamics 365 Customer Engagement Plan" 'DYN365_ENTERPRISE_SALES' = "Dynamics 365 for Sales Enterprise Edition" 'DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE' = "Dynamics 365 for Sales and Customer Service Enterprise Edition" 'DYN365_Enterprise_Talent_Attract_TeamMember' = "DYNAMICS 365 FOR TALENT - ATTRACT EXPERIENCE TEAM MEMBER" 'DYN365_Enterprise_Talent_Onboard_TeamMember' = "DYNAMICS 365 FOR TALENT - ONBOARD EXPERIENCE" 'DYN365_ENTERPRISE_TEAM_MEMBERS' = "Dynamics 365 for Team Members Enterprise Edition" 'DYN365_FINANCE' = "Dynamics 365 Finance" 'DYN365_FINANCIALS_ACCOUNTANT' = "Dynamics 365 Business Central External Accountant" 'DYN365_FINANCIALS_ACCOUNTANT_SKU' = "Dynamics 365 Business Central External Accountant" 'DYN365_FINANCIALS_BUSINESS' = "Dynamics 365 for Business Central Essentials" 'DYN365_FINANCIALS_BUSINESS_SKU' = "Dynamics 365 for Financials Business Edition" 'DYN365_FINANCIALS_TEAM_MEMBERS' = "Dynamics 365 for Team Members" 'DYN365_FS_ENTERPRISE_VIRAL_TRIAL' = "Dynamics 365 Field Service Enterprise vTrial" 'DYN365_IOT_INTELLIGENCE_ADDL_MACHINES' = "Sensor Data Intelligence Additional Machines Add-in for Dynamics 365 Supply Chain Management" 'DYN365_IOT_INTELLIGENCE_SCENARIO' = "Sensor Data Intelligence Scenario Add-in for Dynamics 365 Supply Chain Management" 'DYN365_MARKETING_50K_CONTACT_ADDON' = "Dynamics 365 for Marketing 50K Addnl Contacts" 'DYN365_MARKETING_APP' = "Dynamics 365 for Marketing" 'DYN365_MARKETING_APP_ATTACH' = "Dynamics 365 for Marketing Attach" 'DYN365_MARKETING_APPLICATION_ADDON' = "Dynamics 365 for Marketing Additional Application" 'DYN365_MARKETING_CONTACT_ADDON_T3' = "Dynamics 365 for Marketing Addnl Contacts Tier 3" 'DYN365_MARKETING_CONTACT_ADDON_T5' = "Dynamics 365 for Marketing Addnl Contacts Tier 5" 'DYN365_MARKETING_MSE_USER' = "Dynamics 365 for Marketing MSE User" 'DYN365_MARKETING_SANDBOX_APPLICATION_ADDON' = "Dynamics 365 for Marketing Additional Non-Prod Application" 'DYN365_MARKETING_USER' = "Dynamics 365 for Marketing USL" 'DYN365_REGULATORY_SERVICE' = "Dynamics 365 Regulatory Service - Enterprise Edition Trial" 'DYN365_RETAIL_DEVICE' = "Dynamics 365 for Retail Device" 'DYN365_RETAIL_TRIAL' = "Dynamics 365 Commerce Trial" 'DYN365_SALES_ENTERPRISE_VIRAL_TRIAL' = "Dynamics 365 Sales Enterprise vTrial" 'DYN365_SALES_INSIGHTS' = "Dynamics 365 AI for Sales (Embedded)" 'DYN365_SALES_INSIGHTS_VIRAL_TRIAL' = "Dynamics 365 Sales Insights vTrial" 'DYN365_SALES_PREMIUM' = "Dynamics 365 Sales Premium" 'DYN365_SALES_PRO' = "Dynamics 365 for Sales Professional" 'DYN365_SCM' = "Dynamics 365 for Supply Chain Management" 'DYN365_TALENT_ENTERPRISE' = "DYNAMICS 365 FOR TALENT" 'DYN365_TEAM_MEMBERS' = "Dynamics 365 Team Members" 'DYN365BC_MS_INVOICING' = "Microsoft Invoicing" 'Dynamics_365_Customer_Service_Enterprise_admin_trial' = "Dynamics 365 Customer Service Enterprise Admin" 'Dynamics_365_Customer_Service_Enterprise_viral_trial' = "Dynamics 365 Customer Service Enterprise Viral Trial" 'Dynamics_365_Field_Service_Enterprise_viral_trial' = "Dynamics 365 Field Service Viral Trial" 'Dynamics_365_for_HCM_Trial' = "Dynamics 365 for HCM Trial" 'Dynamics_365_for_Operations' = "Dynamics 365 UNF OPS Plan ENT Edition" 'Dynamics_365_for_Operations_Devices' = "Dynamics 365 Operations - Device" 'Dynamics_365_for_Operations_Sandbox_Tier2' = "Dynamics 365 for Operations non-production multi-box instance for standard acceptance testing (Tier 2)" 'Dynamics_365_for_Operations_Sandbox_Tier2_SKU' = "Dynamics 365 Operations - Sandbox Tier 2:Standard Acceptance Testing" 'Dynamics_365_for_Operations_Sandbox_Tier4' = "Dynamics 365 for Operations Enterprise Edition - Sandbox Tier 4:Standard Performance Testing" 'Dynamics_365_for_Operations_Sandbox_Tier4_SKU' = "Dynamics 365 Operations - Sandbox Tier 4:Standard Performance Testing" 'DYNAMICS_365_FOR_OPERATIONS_TEAM_MEMBERS' = "DYNAMICS 365 FOR OPERATIONS TEAM MEMBERS" 'Dynamics_365_for_OperationsDevices' = "Dynamics 365 for Operations Devices" 'Dynamics_365_for_Retail' = "DYNAMICS 365 FOR RETAIL" 'Dynamics_365_for_Retail_Team_members' = "DYNAMICS 365 FOR RETAIL TEAM MEMBERS" 'Dynamics_365_for_Talent_Team_members' = "DYNAMICS 365 FOR TALENT TEAM MEMBERS" 'Dynamics_365_Hiring_Free_PLAN' = "Dynamics 365 for Talent: Attract" 'Dynamics_365_Hiring_SKU' = "Dynamics 365 Talent: Attract" 'Dynamics_365_Onboarding_Free_PLAN' = "Dynamics 365 for Talent: Onboard" 'DYNAMICS_365_ONBOARDING_SKU' = "Dynamics 365 Talent: Onboard" 'Dynamics_365_Sales_Field_Service_and_Customer_Service_Partner_Sandbox' = "Dynamics 365 Sales, Field Service and Customer Service Partner Sandbox" 'Dynamics_365_Sales_Premium_Viral_Trial' = "Dynamics 365 Sales Premium Viral Trial" 'Dynamics_365_Talent_Onboard' = "DYNAMICS 365 FOR TALENT: ONBOARD" 'DYNB365_CSI_VIRAL_TRIAL' = "Dynamics 365 Customer Service Insights vTrial" 'E3_VDA_only' = "Windows 10/11 Enterprise E3 VDA" 'EducationAnalyticsP1' = "Education Analytics" 'EMS' = "Enterprise Mobility + Security E3" 'EMS_EDU_FACULTY' = "Enterprise Mobility + Security A3 for Faculty" 'EMS_GOV' = "Enterprise Mobility + Security G3 GCC" 'EMSPREMIUM' = "Enterprise Mobility + Security E5" 'EMSPREMIUM_GOV' = "Enterprise Mobility + Security G5 GCC" 'ENTERPRISEPACK' = "Office 365 E3" 'ENTERPRISEPACK_GOV' = "Office 365 G3 GCC" 'ENTERPRISEPACK_USGOV_DOD' = "Office 365 E3_USGOV_DOD" 'ENTERPRISEPACK_USGOV_GCCHIGH' = "Office 365 E3_USGOV_GCCHIGH" 'ENTERPRISEPACKPLUS_FACULTY' = "Office 365 A3 for faculty" 'ENTERPRISEPACKPLUS_STUDENT' = "Office 365 A3 for students" 'ENTERPRISEPREMIUM' = "Office 365 E5" 'ENTERPRISEPREMIUM_FACULTY' = "Office 365 A5 for faculty" 'ENTERPRISEPREMIUM_GOV' = "Office 365 G5 GCC" 'ENTERPRISEPREMIUM_NOPSTNCONF' = "Office 365 E5 Without Audio Conferencing" 'ENTERPRISEPREMIUM_STUDENT' = "Office 365 A5 for students" 'ENTERPRISEWITHSCAL' = "Office 365 E4" 'EOP_ENTERPRISE' = "Exchange Online Protection" 'EOP_ENTERPRISE_PREMIUM' = "Exchange Enterprise CAL Services (EOP DLP)" 'EQUIVIO_ANALYTICS' = "Office 365 Advanced Compliance" 'EQUIVIO_ANALYTICS_GOV' = "Office 365 Advanced Compliance for GCC" 'ERP_TRIAL_INSTANCE' = "Dynamics 365 Operations Trial Environment" 'EXCEL_PREMIUM' = "Microsoft Excel Advanced Analytics" 'EXCHANGE_ANALYTICS' = "Microsoft MyAnalytics (Full)" 'EXCHANGE_ANALYTICS_GOV' = "Microsoft MyAnalytics for Government (Full)" 'EXCHANGE_B_STANDARD' = "EXCHANGE ONLINE POP" 'EXCHANGE_FOUNDATION_GOV' = "EXCHANGE FOUNDATION FOR GOVERNMENT" 'EXCHANGE_L_STANDARD' = "EXCHANGE ONLINE (P1)" 'EXCHANGE_S_ARCHIVE' = "EXCHANGE ONLINE ARCHIVING FOR EXCHANGE SERVER" 'EXCHANGE_S_ARCHIVE_ADDON' = "EXCHANGE ONLINE ARCHIVING FOR EXCHANGE ONLINE" 'EXCHANGE_S_DESKLESS' = "EXCHANGE ONLINE KIOSK" 'EXCHANGE_S_DESKLESS_GOV' = "Exchange Online (Kiosk) for Government" 'EXCHANGE_S_ENTERPRISE' = "EXCHANGE ONLINE (PLAN 2)" 'EXCHANGE_S_ENTERPRISE_GOV' = "Exchange Online (Plan 2) for Government" 'EXCHANGE_S_ESSENTIALS' = "Exchange Online Essentials" 'EXCHANGE_S_FOUNDATION' = "Exchange Foundation" 'EXCHANGE_S_FOUNDATION_GOV' = "Exchange Foundation for Government" 'EXCHANGE_S_STANDARD' = "Exchange Online (Plan 1)" 'EXCHANGE_S_STANDARD_GOV' = "Exchange Online (Plan 1) for Government" 'EXCHANGE_S_STANDARD_MIDMARKET' = "EXCHANGE ONLINE PLAN" 'EXCHANGEARCHIVE' = "Exchange Online Archiving for Exchange Server" 'EXCHANGEARCHIVE_ADDON' = "Exchange Online Archiving for Exchange Online" 'EXCHANGEDESKLESS' = "Exchange Online Kiosk" 'EXCHANGEENTERPRISE' = "Exchange Online (Plan 2)" 'EXCHANGEENTERPRISE_FACULTY' = "Exchange Online (Plan 2) for Faculty" 'EXCHANGEESSENTIALS' = "Exchange Online Essentials (ExO P1 Based)" 'EXCHANGEONLINE_MULTIGEO' = "Exchange Online Multi-Geo" 'EXCHANGESTANDARD' = "Exchange Online (Plan 1)" 'EXCHANGESTANDARD_ALUMNI' = "Exchange Online (Plan 1) for Alumni with Yammer" 'EXCHANGESTANDARD_GOV' = "Exchange Online (Plan 1) for GCC" 'EXCHANGESTANDARD_STUDENT' = "Exchange Online (Plan 1) for Students" 'EXCHANGETELCO' = "Exchange Online POP" 'EXPERTS_ON_DEMAND' = "Microsoft Threat Experts - Experts on Demand" 'FLOW_BUSINESS_PROCESS' = "Power Automate per flow plan" 'FLOW_CCI_BOTS' = "Flow for CCI Bots" 'FLOW_CUSTOMER_SERVICE_PRO' = "Power Automate for Customer Service Pro" 'FLOW_DEV_VIRAL' = "Flow for Developer" 'FLOW_DYN_APPS' = "Flow for Dynamics 365" 'FLOW_DYN_P2' = "Flow for Dynamics 365" 'FLOW_DYN_TEAM' = "Power Automate for Dynamics 365" 'FLOW_FOR_PROJECT' = "Flow for Project" 'FLOW_FORMS_PRO' = "Power Automate for Dynamics 365 Customer Voice" 'FLOW_FREE' = "Microsoft Power Automate Free" 'FLOW_O365_P1' = "FLOW FOR OFFICE 365" 'FLOW_O365_P1_GOV' = "Power Automate for Office 365 for Government" 'FLOW_O365_P2' = "Power Automate for Office 365" 'FLOW_O365_P2_GOV' = "POWER AUTOMATE FOR OFFICE 365 FOR GOVERNMENT" 'FLOW_O365_P3' = "Power Automate for Office 365" 'FLOW_O365_P3_GOV' = "Power Automate for Office 365 for Government" 'FLOW_O365_S1' = "Power Automate for Office 365 F3" 'FLOW_O365_S1_GOV' = "Power Automate for Office 365 F3 for Government" 'FLOW_P1_GOV' = "Power Automate (Plan 1) for Government" 'FLOW_P2' = "Microsoft Power Automate Plan 2" 'FLOW_P2_VIRAL' = "Flow Free" 'FLOW_P2_VIRAL_REAL' = "Flow P2 Viral" 'Flow_Per_APP' = "Power Automate for Power Apps per App Plan" 'Flow_Per_APP_IWTRIAL' = "Flow per app baseline access" 'FLOW_PER_USER' = "Power Automate per user plan" 'FLOW_PER_USER_DEPT' = "Power Automate per user plan dept" 'FLOW_PER_USER_GCC' = "Power Automate per user plan for Government" 'Flow_PowerApps_PerUser' = "Power Automate for Power Apps per User Plan" 'Flow_PowerApps_PerUser_GCC' = "Power Automate for Power Apps per User Plan for GCC" 'FLOW_VIRTUAL_AGENT_BASE' = "Power Automate for Virtual Agent" 'FLOW_VIRTUAL_AGENT_USL' = "Power Automate for Virtual Agent" 'FORMS_GOV_E1' = "Forms for Government (Plan E1)" 'FORMS_GOV_E3' = "FORMS FOR GOVERNMENT (PLAN E3)" 'FORMS_GOV_E5' = "Microsoft Forms for Government (Plan E5)" 'FORMS_GOV_F1' = "Forms for Government (Plan F1)" 'FORMS_PLAN_E1' = "MICROSOFT FORMS (PLAN E1)" 'FORMS_PLAN_E3' = "Microsoft Forms (Plan E3)" 'FORMS_PLAN_E5' = "Microsoft Forms (Plan E5)" 'FORMS_PLAN_K' = "Microsoft Forms (Plan F1)" 'FORMS_PRO' = "Dynamics 365 Customer Voice Trial" 'Forms_Pro_AddOn' = "Dynamics 365 Customer Voice Additional Responses" 'Forms_Pro_CE' = "Microsoft Dynamics 365 Customer Voice for Customer Engagement Plan" 'Forms_Pro_Customer_Insights' = "Microsoft Dynamics 365 Customer Voice for Customer Insights" 'Forms_Pro_FS' = "Microsoft Dynamics 365 Customer Voice for Field Service" 'Forms_Pro_Marketing' = "Microsoft Dynamics 365 Customer Voice for Marketing" 'Forms_Pro_Marketing_App' = "Microsoft Dynamics 365 Customer Voice for Marketing Application" 'Forms_Pro_Relationship_Sales' = "Microsoft Dynamics 365 Customer Voice for Relationship Sales" 'Forms_Pro_SalesEnt' = "Microsoft Dynamics 365 Customer Voice for Sales Enterprise" 'Forms_Pro_Service' = "Microsoft Dynamics 365 Customer Voice for Customer Service Enterprise" 'Forms_Pro_USL' = "Dynamics 365 Customer Voice USL" 'GRAPH_CONNECTORS_SEARCH_INDEX' = "Graph Connectors Search with Index" 'GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP' = "Graph Connectors Search with Index (Microsoft Viva Topics)" 'GUIDES' = "Dynamics 365 Guides" 'GUIDES_USER' = "Dynamics 365 Guides" 'IDENTITY_THREAT_PROTECTION' = "Microsoft 365 E5 Security" 'IDENTITY_THREAT_PROTECTION_FOR_EMS_E5' = "Microsoft 365 E5 Security for EMS E5" 'INFO_GOVERNANCE' = "Microsoft Information Governance" 'INFORMATION_BARRIERS' = "Information Barriers" 'INFORMATION_PROTECTION_COMPLIANCE' = "Microsoft 365 E5 Compliance" 'INSIDER_RISK' = "Microsoft Insider Risk Management" 'INSIDER_RISK_MANAGEMENT' = "Microsoft Insider Risk Management" 'Intelligent_Content_Services' = "SharePoint Syntex" 'Intelligent_Content_Services_SPO_type' = "SharePoint Syntex - SPO type" 'INTUNE_A' = "Intune" 'INTUNE_A_D' = "Microsoft Intune Device" 'INTUNE_A_D_GOV' = "Microsoft Intune Device for Government" 'Intune_AdvancedEA' = "Intune Advanced endpoint analytics" 'Intune_Defender' = "MDE_SecurityManagement" 'INTUNE_EDU' = "Intune for Education" 'INTUNE_O365' = "Mobile Device Management for Office 365" 'INTUNE_P2' = "Intune Plan 2" 'INTUNE_SMB' = "Microsoft Intune SMB" 'INTUNE_SMBIZ' = "Microsoft Intune" 'Intune-EPM' = "Intune Endpoint Privilege Management" 'Intune-MAMTunnel' = "Microsoft Tunnel for Mobile Application Management" 'IT_ACADEMY_AD' = "Microsoft Imagine Academy" 'KAIZALA_O365_P1' = "Microsoft Kaizala Pro" 'KAIZALA_O365_P2' = "Microsoft Kaizala Pro" 'KAIZALA_O365_P3' = "Microsoft Kaizala Pro" 'KAIZALA_STANDALONE' = "Microsoft Kaizala Pro" 'LITEPACK' = "Office 365 Small Business" 'LITEPACK_P2' = "Office 365 Small Business Premium" 'LOCKBOX_ENTERPRISE' = "Customer Lockbox" 'LOCKBOX_ENTERPRISE_GOV' = "Customer Lockbox for Government" 'M365_A5_SUITE_COMPONENTS_FACULTY' = "Microsoft 365 A5 Suite features for faculty" 'M365_ADVANCED_AUDITING' = "Microsoft 365 Advanced Auditing" 'M365_AUDIT_PLATFORM' = "Microsoft 365 Audit Platform" 'M365_E5_SUITE_COMPONENTS' = "Microsoft 365 E5 Suite features" 'M365_F1' = "Microsoft 365 F1" 'M365_F1_COMM' = "Microsoft 365 F1" 'M365_F1_GOV' = "Microsoft 365 F3 GCC" 'M365_G3_GOV' = "Microsoft 365 G3 GCC" 'M365_G5_GCC' = "Microsoft 365 GCC G5" 'M365_LIGHTHOUSE_CUSTOMER_PLAN1' = "Microsoft 365 Lighthouse (Plan 1)" 'M365_LIGHTHOUSE_PARTNER_PLAN1' = "Microsoft 365 Lighthouse (Plan 2)" 'M365_SECURITY_COMPLIANCE_FOR_FLW' = "Microsoft 365 Security and Compliance for Firstline Workers" 'M365EDU_A1' = "Microsoft 365 A1" 'M365EDU_A3_FACULTY' = "Microsoft 365 A3 for Faculty" 'M365EDU_A3_STUDENT' = "Microsoft 365 A3 for Students" 'M365EDU_A3_STUUSEBNFT' = "Microsoft 365 A3 for students use benefit" 'M365EDU_A3_STUUSEBNFT_RPA1' = "Microsoft 365 A3 - Unattended License for students use benefit" 'M365EDU_A5_FACULTY' = "Microsoft 365 A5 for Faculty" 'M365EDU_A5_NOPSTNCONF_STUUSEBNFT' = "Microsoft 365 A5 without Audio Conferencing for students use benefit" 'M365EDU_A5_STUDENT' = "Microsoft 365 A5 for Students" 'M365EDU_A5_STUUSEBNFT' = "Microsoft 365 A5 for students use benefit" 'MCO_TEAMS_IW' = "MICROSOFT TEAMS" 'MCO_VIRTUAL_APPT' = "Microsoft Teams Premium Virtual Appointments" 'MCOCAP' = "Microsoft Teams Shared Devices" 'MCOCAP_GOV' = "Microsoft Teams Shared Devices for GCC" 'MCOEV' = "Microsoft Teams Phone Standard" 'MCOEV_DOD' = "Microsoft Teams Phone Standard for DOD" 'MCOEV_FACULTY' = "Microsoft Teams Phone Standard for Faculty" 'MCOEV_GCCHIGH' = "Microsoft Teams Phone Standard for GCCHIGH" 'MCOEV_GOV' = "Microsoft Teams Phone Standard for GCC" 'MCOEV_STUDENT' = "Microsoft Teams Phone Standard for Students" 'MCOEV_TELSTRA' = "Microsoft Teams Phone Standard for TELSTRA" 'MCOEV_USGOV_DOD' = "Microsoft Teams Phone Standard_USGOV_DOD" 'MCOEV_USGOV_GCCHIGH' = "Microsoft Teams Phone Standard_USGOV_GCCHIGH" 'MCOEV_VIRTUALUSER' = "Microsoft 365 Phone Standard Resource Account" 'MCOEV_VIRTUALUSER_GOV' = "Microsoft 365 Phone Standard Resource Account for Government" 'MCOEVSMB' = "SKYPE FOR BUSINESS CLOUD PBX FOR SMALL AND MEDIUM BUSINESS" 'MCOEVSMB_1' = "Microsoft Teams Phone Standard for Small and Medium Business" 'MCOFREE' = "MCO FREE FOR MICROSOFT TEAMS (FREE)" 'MCOIMP' = "Skype for Business Online (Plan 1)" 'MCOIMP_GOV' = "Skype for Business Online (Plan 1) for Government" 'MCOLITE' = "SKYPE FOR BUSINESS ONLINE (PLAN P1)" 'MCOMEETACPEA' = "Microsoft 365 Audio Conferencing Pay-Per-Minute - EA" 'MCOMEETADV' = "Microsoft 365 Audio Conferencing" 'MCOMEETADV_GOV' = "Microsoft 365 Audio Conferencing for GCC" 'MCOMEETBASIC' = "Microsoft Teams Audio Conferencing with dial-out to select geographies" 'MCOPSTN_1_GOV' = "Microsoft 365 Domestic Calling Plan for GCC" 'MCOPSTN_5' = "Microsoft 365 Domestic Calling Plan (120 Minutes)" 'MCOPSTN1' = "Skype for Business PSTN Domestic Calling" 'MCOPSTN1_GOV' = "Domestic Calling for Government" 'MCOPSTN2' = "Skype for Business PSTN Domestic and International Calling" 'MCOPSTN3' = "MCOPSTN3" 'MCOPSTN5' = "Skype for Business PSTN Domestic Calling (120 Minutes)" 'MCOPSTN8' = "Microsoft 365 Domestic Calling Plan (120 min) at User Level" 'MCOPSTNC' = "Communications Credits" 'MCOPSTNEAU' = "AUSTRALIA CALLING PLAN" 'MCOPSTNEAU2' = "TELSTRA Calling for O365" 'MCOPSTNPP' = "Skype for Business PSTN Usage Calling Plan" 'MCOSTANDARD' = "Skype for Business Online (Plan 2)" 'MCOSTANDARD_GOV' = "Skype for Business Online (Plan 2) for Government" 'MCOSTANDARD_MIDMARKET' = "SKYPE FOR BUSINESS ONLINE (PLAN 2) FOR MIDSIZ" 'MCOTEAMS_ESSENTIALS' = "Teams Phone with Calling Plan" 'MCOVOICECONF' = "SKYPE FOR BUSINESS ONLINE (PLAN 3)" 'MCS_BizApps_Cloud_for_Sustainability_vTrial' = "MCS - BizApps_Cloud for Sustainability_vTrial" 'MDATP_Server' = "Microsoft Defender for Endpoint Server" 'MDATP_XPLAT' = "Microsoft Defender for Endpoint P2_XPLAT" 'MDE_LITE' = "Microsoft Defender for Endpoint Plan 1" 'MDE_SMB' = "Microsoft Defender for Business" 'MDM_SALES_COLLABORATION' = "MICROSOFT DYNAMICS MARKETING SALES COLLABORATION - ELIGIBILITY CRITERIA APPLY" 'MEE_FACULTY' = "Minecraft Education Faculty" 'MEE_STUDENT' = "Minecraft Education Student" 'MEETING_ROOM' = "Microsoft Teams Rooms Standard" 'MEETING_ROOM_NOAUDIOCONF' = "Microsoft Teams Rooms Standard without Audio Conferencing" 'MFA_PREMIUM' = "MICROSOFT AZURE MULTI-FACTOR AUTHENTICATION" 'MFA_STANDALONE' = "Microsoft Azure Multi-Factor Authentication" 'Microsoft 365 A3 Suite features for faculty' = "Microsoft 365 A3 Suite features for faculty" 'Microsoft_365_E3' = "Microsoft 365 E3 (500 seats min)_HUB" 'Microsoft_365_E3_Extra_Features' = "Microsoft 365 E3 Extra Features" 'Microsoft_365_E5' = "Microsoft 365 E5 (500 seats min)_HUB" 'Microsoft_365_E5_without_Audio_Conferencing' = "Microsoft 365 E5 without Audio Conferencing (500 seats min)_HUB" 'MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_A' = "Microsoft Application Protection and Governance (A)" 'MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_D' = "Microsoft Application Protection and Governance (D)" 'MICROSOFT_BUSINESS_CENTER' = "Microsoft Business Center" 'Microsoft_Cloud_App_Security_App_Governance_Add_On' = "App governance add-on to Microsoft Defender for Cloud Apps" 'Microsoft_Cloud_for_Sustainability_vTrial' = "Microsoft Cloud for Sustainability vTrial" 'MICROSOFT_COMMUNICATION_COMPLIANCE' = "Microsoft 365 Communication Compliance" 'MICROSOFT_ECDN' = "Microsoft eCDN" 'Microsoft_Intune_Suite' = "Microsoft Intune Suite" 'MICROSOFT_REMOTE_ASSIST' = "Dynamics 365 Remote Assist" 'MICROSOFT_REMOTE_ASSIST_HOLOLENS' = "Dynamics 365 Remote Assist HoloLens" 'MICROSOFT_SEARCH' = "Microsoft Search" 'Microsoft_Teams_Audio_Conferencing_select_dial_out' = "Microsoft Teams Audio Conferencing with dial-out to USA/CAN" 'Microsoft_Teams_Premium' = "Microsoft Teams Premium Introductory Pricing" 'Microsoft_Teams_Rooms_Basic' = "Microsoft Teams Rooms Basic" 'Microsoft_Teams_Rooms_Basic_FAC' = "Microsoft Teams Rooms Basic for EDU" 'Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing' = "Microsoft Teams Rooms Basic without Audio Conferencing" 'Microsoft_Teams_Rooms_Pro' = "Microsoft Teams Rooms Pro" 'Microsoft_Teams_Rooms_Pro_FAC' = "Microsoft Teams Rooms Pro for EDU" 'Microsoft_Teams_Rooms_Pro_without_Audio_Conferencing' = "Microsoft Teams Rooms Pro without Audio Conferencing" 'Microsoft_Viva_Goals' = "Microsoft Viva Goals" 'Microsoft_Viva_Sales_PowerAutomate' = "Microsoft Viva Sales Premium with Power Automate" 'Microsoft_Viva_Sales_PremiumTrial' = "Microsoft Viva Sales Premium & Trial" 'Microsoft365_Lighthouse' = "Microsoft 365 Lighthouse" 'MICROSOFTBOOKINGS' = "Microsoft Bookings" 'MICROSOFTENDPOINTDLP' = "Microsoft Endpoint DLP" 'MICROSOFTSTREAM' = "MICROSOFT STREAM" 'MIDSIZEPACK' = "Office 365 Midsize Business" 'MINECRAFT_EDUCATION_EDITION' = "Minecraft Education Edition" 'MIP_S_CLP1' = "Information Protection for Office 365 - Standard" 'MIP_S_CLP2' = "Information Protection for Office 365 - Premium" 'MIP_S_Exchange' = "Data Classification in Microsoft 365" 'MIP_S_EXCHANGE_CO' = "Data Classification in Microsoft 365 - Company Level" 'ML_CLASSIFICATION' = "Microsoft ML-Based Classification" 'MMR_P1' = "Meeting Room Managed Services" 'MS_TEAMS_IW' = "Microsoft Teams Trial" 'MTP' = "Microsoft 365 Defender" 'MTR_PREM' = "Teams Rooms Premium" 'MTRProManagement' = "Microsoft Teams Rooms Pro Management" 'MYANALYTICS_P2' = "Insights by MyAnalytics" 'MYANALYTICS_P2_GOV' = "INSIGHTS BY MYANALYTICS FOR GOVERNMENT" 'NBENTERPRISE' = "Microsoft Social Engagement Enterprise" 'NBPROFESSIONALFORCRM' = "MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY" 'NONPROFIT_PORTAL' = "Nonprofit Portal" 'Nucleus' = "Nucleus" 'O365_BUSINESS' = "Microsoft 365 Apps for Business" 'O365_BUSINESS_ESSENTIALS' = "Microsoft 365 Business Basic" 'O365_BUSINESS_PREMIUM' = "Microsoft 365 Business Standard" 'O365_SB_Relationship_Management' = "OUTLOOK CUSTOMER MANAGER" 'OFFICE_BUSINESS' = "OFFICE 365 BUSINESS" 'OFFICE_FORMS_PLAN_2' = "Microsoft Forms (Plan 2)" 'OFFICE_FORMS_PLAN_3' = "Microsoft Forms (Plan 3)" 'OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ' = "OFFICE 365 SMALL BUSINESS SUBSCRIPTION" 'OFFICE_PROPLUS_DEVICE' = "Microsoft 365 Apps for Enterprise (Device)" 'OFFICE_PROPLUS_DEVICE1' = "Microsoft 365 Apps for enterprise (device)" 'OFFICE_SHARED_COMPUTER_ACTIVATION' = "Office Shared Computer Activation" 'OFFICE365_MULTIGEO' = "Multi-Geo Capabilities in Office 365" 'OFFICEMOBILE_SUBSCRIPTION' = "OFFICEMOBILE_SUBSCRIPTION" 'OFFICEMOBILE_SUBSCRIPTION_GOV' = "Office Mobile Apps for Office 365 for GCC" 'OFFICESUBSCRIPTION' = "Microsoft 365 Apps for Enterprise" 'OFFICESUBSCRIPTION_FACULTY' = "Microsoft 365 Apps for Faculty" 'OFFICESUBSCRIPTION_GOV' = "Microsoft 365 Apps for enterprise G" 'OFFICESUBSCRIPTION_STUDENT' = "Microsoft 365 Apps for Students" 'OFFICESUBSCRIPTION_unattended' = "Microsoft 365 Apps for Enterprise (Unattended)" 'ONEDRIVE_BASIC' = "OneDrive for business Basic" 'ONEDRIVE_BASIC_GOV' = "ONEDRIVE FOR BUSINESS BASIC FOR GOVERNMENT" 'ONEDRIVE_BASIC_P2' = "OneDrive for Business (Basic 2)" 'ONEDRIVEENTERPRISE' = "ONEDRIVEENTERPRISE" 'ONEDRIVESTANDARD' = "ONEDRIVESTANDARD" 'PAM_ENTERPRISE' = "Office 365 Privileged Access Management" 'PBI_PREMIUM_P1_ADDON' = "Power BI Premium P1" 'PBI_PREMIUM_PER_USER' = "Power BI Premium Per User" 'PBI_PREMIUM_PER_USER_ADDON' = "Power BI Premium Per User Add-On" 'PBI_PREMIUM_PER_USER_DEPT' = "Power BI Premium Per User Dept" 'PBI_PREMIUM_PER_USER_FACULTY' = "Power BI Premium Per User for Faculty" 'PHONESYSTEM_VIRTUALUSER' = "Microsoft Teams Phone Resource Account" 'PHONESYSTEM_VIRTUALUSER_GOV' = "Microsoft Teams Phone Resource Account for GCC" 'POWER_APPS_DYN365_VIRAL_TRIAL' = "Power Apps for Dynamics 365 vTrial" 'POWER_AUTOMATE_ATTENDED_RPA' = "Power Automate RPA Attended" 'POWER_AUTOMATE_DYN365_VIRAL_TRIAL' = "Power Automate for Dynamics 365 vTrial" 'Power_Automate_For_Project_P1' = "POWER AUTOMATE FOR PROJECT P1" 'POWER_AUTOMATE_UNATTENDED_RPA' = "Power Automate Unattended RPA add-on" 'POWER_BI_ADDON' = "Power BI for Office 365 Add-On" 'POWER_BI_INDIVIDUAL_USER' = "Power BI" 'POWER_BI_PRO' = "Power BI Pro" 'POWER_BI_PRO_CE' = "Power BI Pro CE" 'POWER_BI_PRO_DEPT' = "Power BI Pro Dept" 'POWER_BI_PRO_FACULTY' = "Power BI Pro for Faculty" 'POWER_BI_STANDARD' = "Power BI (free)" 'POWER_BI_STANDARD_FACULTY' = "Microsoft Fabric (Free) for faculty" 'POWER_BI_STANDARD_STUDENT' = "Microsoft Fabric (Free) for student" 'Power_Pages_Internal_User' = "Power Pages Internal User" 'POWER_PAGES_VTRIAL' = "Power Pages vTrial for Makers" 'Power_Pages_vTrial_for_Makers' = "Power Pages vTrial for Makers" 'POWER_VIRTUAL_AGENTS_D365_CS_CHAT' = "Power Virtual Agents for Chat" 'POWER_VIRTUAL_AGENTS_D365_CS_MESSAGING' = "Power Virtual Agents for Digital Messaging" 'POWER_VIRTUAL_AGENTS_D365_CS_VOICE' = "Power Virtual Agents for Customer Service Voice" 'POWER_VIRTUAL_AGENTS_O365_F1' = "Power Virtual Agents for Office 365" 'POWER_VIRTUAL_AGENTS_O365_P1' = "POWER VIRTUAL AGENTS FOR OFFICE 365 P1" 'POWER_VIRTUAL_AGENTS_O365_P2' = "Power Virtual Agents for Office 365" 'POWER_VIRTUAL_AGENTS_O365_P3' = "Power Virtual Agents for Office 365" 'POWERAPPS_CUSTOMER_SERVICE_PRO' = "Power Apps for Customer Service Pro" 'POWERAPPS_DEV' = "Microsoft Power Apps for Developer" 'POWERAPPS_DEV_VIRAL' = "PowerApps for Developer" 'POWERAPPS_DYN_APPS' = "PowerApps for Dynamics 365" 'POWERAPPS_DYN_P2' = "Power Apps for Dynamics 365" 'POWERAPPS_DYN_TEAM' = "Power Apps for Dynamics 365" 'POWERAPPS_GUIDES' = "Power Apps for Guides" 'POWERAPPS_INDIVIDUAL_USER' = "Power Apps and Logic Flows" 'POWERAPPS_O365_P1' = "POWERAPPS FOR OFFICE 365" 'POWERAPPS_O365_P1_GOV' = "Power Apps for Office 365 for Government" 'POWERAPPS_O365_P2' = "Power Apps for Office 365" 'POWERAPPS_O365_P2_GOV' = "POWER APPS FOR OFFICE 365 FOR GOVERNMENT" 'POWERAPPS_O365_P3' = "Power Apps for Office 365 (Plan 3)" 'POWERAPPS_O365_P3_GOV' = "Power Apps for Office 365 for Government" 'POWERAPPS_O365_S1' = "Power Apps for Office 365 F3" 'POWERAPPS_O365_S1_GOV' = "Power Apps for Office 365 F3 for Government" 'POWERAPPS_P1_GOV' = "PowerApps Plan 1 for Government" 'POWERAPPS_P2' = "Power Apps (Plan 2)" 'POWERAPPS_P2_VIRAL' = "PowerApps Trial" 'POWERAPPS_PER_APP' = "Power Apps per app plan" 'POWERAPPS_PER_APP_IW' = "PowerApps per app baseline access" 'POWERAPPS_PER_APP_IWTRIAL' = "PowerApps per app baseline access" 'POWERAPPS_PER_APP_NEW' = "Power Apps per app plan (1 app or portal)" 'POWERAPPS_PER_USER' = "Power Apps per user plan" 'POWERAPPS_PER_USER_GCC' = "Power Apps per user plan for Government" 'POWERAPPS_PORTALS_LOGIN' = "Power Apps Portals Login Capacity Add-On" 'POWERAPPS_PORTALS_LOGIN_GCC' = "Power Apps Portals Login Capacity Add-On for Government" 'POWERAPPS_PORTALS_LOGIN_T2' = "Power Apps Portals login capacity add-on Tier 2 (10 unit min)" 'POWERAPPS_PORTALS_LOGIN_T2_GCC' = "Power Apps Portals login capacity add-on Tier 2 (10 unit min) for Government" 'POWERAPPS_PORTALS_LOGIN_T3' = "Power Apps Portals login capacity add-on Tier 3 (50 unit min)" 'POWERAPPS_PORTALS_PAGEVIEW' = "Power Apps Portals page view capacity add-on" 'POWERAPPS_PORTALS_PAGEVIEW_GCC' = "Power Apps Portals page view capacity add-on for Government" 'POWERAPPS_SALES_PRO' = "Power Apps for Sales Pro" 'POWERAPPS_VIRAL' = "Microsoft Power Apps Plan 2 Trial" 'POWERAPPSFREE' = "MICROSOFT POWERAPPS" 'POWERAUTOMATE_ATTENDED_RPA' = "Power Automate per user with attended RPA plan" 'POWERAUTOMATE_DESKTOP_FOR_WIN' = "PAD for Windows" 'POWERAUTOMATE_UNATTENDED_RPA' = "Power Automate unattended RPA add-on" 'POWERBI_PRO_GOV' = "Power BI Pro for GCC" 'POWERFLOW_P2' = "Microsoft Power Apps Plan 2 (Qualified Offer)" 'POWERFLOWSFREE' = "LOGIC FLOWS" 'POWERVIDEOSFREE' = "MICROSOFT POWER VIDEOS BASIC" 'PREMIUM_ENCRYPTION' = "Premium Encryption in Office 365" 'PRIVACY_MANAGEMENT_RISK' = "Privacy Management � risk" 'PRIVACY_MANAGEMENT_RISK_EDU' = "Privacy Management - risk for EDU" 'PRIVACY_MANAGEMENT_RISK_GCC' = "Privacy Management - risk GCC" 'PRIVACY_MANAGEMENT_RISK_USGOV_DOD' = "Privacy Management - risk_USGOV_DOD" 'PRIVACY_MANAGEMENT_RISK_USGOV_GCCHIGH' = "Privacy Management - risk_USGOV_GCCHIGH" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_EDU_V2' = "Privacy Management - subject rights request (1) for EDU" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2' = "Privacy Management - subject rights request (1)" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_GCC' = "Privacy Management - subject rights request (1) GCC" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_DOD' = "Privacy Management - subject rights request (1) USGOV_DOD" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_GCCHIGH' = "Privacy Management - subject rights request (1) USGOV_GCCHIGH" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_EDU_V2' = "Privacy Management - subject rights request (10) for EDU" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2' = "Privacy Management - subject rights request (10)" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_GCC' = "Privacy Management - subject rights request (10) GCC" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_DOD' = "Privacy Management - subject rights request (10) USGOV_DOD" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_GCCHIGH' = "Privacy Management - subject rights request (10) USGOV_GCCHIGH" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_EDU_V2' = "Privacy Management - subject rights request (100) for EDU" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2' = "Privacy Management - subject rights request (100)" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_GCC' = "Privacy Management - subject rights request (100) GCC" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_DOD' = "Privacy Management - subject rights request (100) USGOV_DOD" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_GCCHIGH' = "Privacy Management - subject rights request (100) USGOV_GCCHIGH" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50' = "Privacy Management - subject rights request (50)" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_EDU_V2' = "Privacy Management - subject rights request (50) for EDU" 'PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_V2' = "Privacy Management - subject rights request (50)" 'PRIVACY_MANGEMENT_DSR' = "Privacy Management - Subject Rights Request" 'PRIVACY_MANGEMENT_DSR_1' = "Privacy Management - Subject Rights Request (1 - Exchange)" 'PRIVACY_MANGEMENT_DSR_10' = "Privacy Management - Subject Rights Request (10)" 'PRIVACY_MANGEMENT_DSR_100' = "Privacy Management - Subject Rights Request (100)" 'PRIVACY_MANGEMENT_DSR_EXCHANGE' = "Privacy Management - Subject Rights Request (Exchange)" 'PRIVACY_MANGEMENT_DSR_EXCHANGE_1' = "Privacy Management - Subject Rights Request (1)" 'PRIVACY_MANGEMENT_DSR_EXCHANGE_10' = "Privacy Management - Subject Rights Request (10 - Exchange)" 'PRIVACY_MANGEMENT_DSR_EXCHANGE_100' = "Privacy Management - Subject Rights Request (100 - Exchange)" 'PRIVACY_MANGEMENT_RISK' = "Priva - Risk" 'PRIVACY_MANGEMENT_RISK_EXCHANGE' = "Priva - Risk (Exchange)" 'PROJECT_CLIENT_SUBSCRIPTION' = "Project Online Desktop Client" 'PROJECT_CLIENT_SUBSCRIPTION_GOV' = "Project Online Desktop Client for Government" 'PROJECT_ESSENTIALS' = "Project Online Essentials" 'PROJECT_ESSENTIALS_GOV' = "Project Online Essentials for Government" 'PROJECT_FOR_PROJECT_OPERATIONS' = "Project for Project Operations" 'PROJECT_MADEIRA_PREVIEW_IW' = "Dynamics 365 Business Central for IWs" 'PROJECT_MADEIRA_PREVIEW_IW_SKU' = "Dynamics 365 Business Central for IWs" 'PROJECT_O365_F3' = "Project for Office (Plan F)" 'PROJECT_O365_P1' = "Project for Office (Plan E1)" 'PROJECT_O365_P2' = "Project for Office (Plan E3)" 'PROJECT_O365_P3' = "Project for Office (Plan E5)" 'PROJECT_P1' = "Project Plan 1" 'PROJECT_PLAN1_DEPT' = "Project Plan 1 (for Department)" 'PROJECT_PLAN3_DEPT' = "Project Plan 3 (for Department)" 'PROJECT_PROFESSIONAL' = "Project P3" 'PROJECT_PROFESSIONAL_FACULTY' = "Project P3 for Faculty" 'PROJECTCLIENT' = "Project for Office 365" 'PROJECTESSENTIALS' = "Project Online Essentials" 'PROJECTESSENTIALS_FACULTY' = "Project Online Essentials for Faculty" 'PROJECTESSENTIALS_GOV' = "Project Online Essentials for GCC" 'PROJECTONLINE_PLAN_1' = "Project Online Premium Without Project Client" 'PROJECTONLINE_PLAN_1_FACULTY' = "Project Plan 5 without Project Client for Faculty" 'PROJECTONLINE_PLAN_2' = "Project Online With Project for Office 365" 'PROJECTPREMIUM' = "Project Online Premium" 'PROJECTPREMIUM_GOV' = "Project Plan 5 for GCC" 'PROJECTPROFESSIONAL' = "Project Plan 3" 'PROJECTPROFESSIONAL_FACULTY' = "Project Plan 3 for Faculty" 'PROJECTPROFESSIONAL_GOV' = "Project Plan 3 for GCC" 'PROJECTWORKMANAGEMENT' = "Microsoft Planner" 'PROJECTWORKMANAGEMENT_GOV' = "Office 365 Planner for Government" 'RECORDS_MANAGEMENT' = "Microsoft Records Management" 'REMOTE_HELP' = "Remote help" 'RIGHTSMANAGEMENT' = "Azure Information Protection Plan 1" 'RIGHTSMANAGEMENT_ADHOC' = "Rights Management Adhoc" 'RMS_S_ADHOC' = "Rights Management Adhoc" 'RMS_S_BASIC' = "Microsoft Azure Rights Management Service" 'RMS_S_ENTERPRISE' = "AZURE INFORMATION PROTECTION PREMIUM P1" 'RMS_S_ENTERPRISE)' = "Microsoft Azure Active Directory Rights" 'RMS_S_ENTERPRISE_GOV' = "Azure Rights Management" 'RMS_S_PREMIUM' = "MICROSOFT AZURE ACTIVE DIRECTORY RIGHTS" 'RMS_S_PREMIUM_GOV' = "Azure Information Protection Premium P1 for GCC" 'RMS_S_PREMIUM2' = "AZURE INFORMATION PROTECTION PREMIUM P2" 'RMS_S_PREMIUM2_GOV' = "Azure Information Protection Premium P2 for GCC" 'RMSBASIC' = "Rights Management Service Basic Content Protection" 'SAFEDOCS' = "Office 365 SafeDocs" 'SCHOOL_DATA_SYNC_P1' = "School Data Sync (Plan 1)" 'SCHOOL_DATA_SYNC_P2' = "School Data Sync (Plan 2)" 'SharePoint Plan 1G' = "SharePoint Plan 1G" 'SHAREPOINT_PROJECT' = "Project Online Service" 'SHAREPOINT_PROJECT_EDU' = "Project Online Service for Education" 'SHAREPOINT_PROJECT_GOV' = "Project Online Service for Government" 'SHAREPOINT_S_DEVELOPER' = "SHAREPOINT FOR DEVELOPER" 'SHAREPOINTDESKLESS' = "SharePoint Online Kiosk" 'SHAREPOINTDESKLESS_GOV' = "SharePoint KioskG" 'SHAREPOINTENTERPRISE' = "SharePoint Online (Plan 2)" 'SHAREPOINTENTERPRISE_EDU' = "SharePoint (Plan 2) for Education" 'SHAREPOINTENTERPRISE_GOV' = "SharePoint Plan 2G" 'SHAREPOINTENTERPRISE_MIDMARKET' = "SHAREPOINT PLAN 1" 'SHAREPOINTLITE' = "SHAREPOINTLITE" 'SHAREPOINTONLINE_MULTIGEO' = "SharePoint Multi-Geo" 'SHAREPOINTSTANDARD' = "SharePoint Online (Plan 1)" 'SHAREPOINTSTANDARD_EDU' = "SharePoint (Plan 1) for Education" 'SHAREPOINTSTORAGE' = "Office 365 Extra File Storage" 'SHAREPOINTSTORAGE_GOV' = "Office 365 Extra File Storage for GCC" 'SHAREPOINTWAC' = "Office for the web" 'SHAREPOINTWAC_DEVELOPER' = "OFFICE ONLINE FOR DEVELOPER" 'SHAREPOINTWAC_EDU' = "Office for the Web for Education" 'SHAREPOINTWAC_GOV' = "Office for the Web for Government" 'SKU_Dynamics_365_for_HCM_Trial' = "Dynamics 365 for Talent" 'SMB_APPS' = "Business Apps (free)" 'SMB_BUSINESS' = "Microsoft 365 Apps for Business" 'SMB_BUSINESS_ESSENTIALS' = "Microsoft 365 Business Basic" 'SMB_BUSINESS_PREMIUM' = "Microsoft 365 Business Standard - Prepaid Legacy" 'SOCIAL_ENGAGEMENT_APP_USER' = "Dynamics 365 AI for Market Insights (Preview)" 'SPB' = "Microsoft 365 Business Premium" 'SPE_E3' = "Microsoft 365 E3" 'SPE_E3_RPA1' = "Microsoft 365 E3 - Unattended License" 'SPE_E3_USGOV_DOD' = "Microsoft 365 E3_USGOV_DOD" 'SPE_E3_USGOV_GCCHIGH' = "Microsoft 365 E3_USGOV_GCCHIGH" 'SPE_E5' = "Microsoft 365 E5" 'SPE_E5_CALLINGMINUTES' = "Microsoft 365 E5 with Calling Minutes" 'SPE_E5_NOPSTNCONF' = "Microsoft 365 E5 without Audio Conferencing" 'SPE_F1' = "Microsoft 365 F3" 'SPE_F5_COMP' = "Microsoft 365 F5 Compliance Add-on" 'SPE_F5_COMP_AR_D_USGOV_DOD' = "Microsoft 365 F5 Compliance Add-on AR (DOD)_USGOV_DOD" 'SPE_F5_COMP_AR_USGOV_GCCHIGH' = "Microsoft 365 F5 Compliance Add-on AR_USGOV_GCCHIGH" 'SPE_F5_COMP_GCC' = "Microsoft 365 F5 Compliance Add-on GCC" 'SPE_F5_SEC' = "Microsoft 365 F5 Security Add-on" 'SPE_F5_SECCOMP' = "Microsoft 365 F5 Security + Compliance Add-on" 'SPZA' = "APP CONNECT" 'SPZA_IW' = "App Connect IW" 'SQL_IS_SSIM' = "Microsoft Power BI Information Services Plan 1" 'STANDARDPACK' = "Office 365 E1" 'STANDARDPACK_GOV' = "Office 365 G1 GCC" 'STANDARDWOFFPACK' = "Office 365 E2" 'STANDARDWOFFPACK_FACULTY' = "Office 365 A1 for faculty" 'STANDARDWOFFPACK_IW_FACULTY' = "Office 365 A1 Plus for faculty" 'STANDARDWOFFPACK_IW_STUDENT' = "Office 365 A1 Plus for students" 'STANDARDWOFFPACK_STUDENT' = "Office 365 A1 for students" 'STREAM' = "Microsoft Stream" 'STREAM_O365_E1' = "Microsoft Stream for Office 365 E1" 'STREAM_O365_E1_GOV' = "Microsoft Stream for O365 for Government (E1)" 'STREAM_O365_E3' = "Microsoft Stream for Office 365 E3" 'STREAM_O365_E3_GOV' = "MICROSOFT STREAM FOR O365 FOR GOVERNMENT (E3)" 'STREAM_O365_E5' = "Microsoft Stream for Office 365 E5" 'STREAM_O365_E5_GOV' = "Stream for Office 365 for Government (E5)" 'STREAM_O365_K' = "Microsoft Stream for O365 K SKU" 'STREAM_O365_K_GOV' = "Microsoft Stream for O365 for Government (F1)" 'STREAM_O365_SMB' = "Stream for Office 365" 'STREAM_P2' = "Microsoft Stream Plan 2" 'STREAM_STORAGE' = "Microsoft Stream Storage Add-On (500 GB)" 'SWAY' = "Sway" 'TEAMS_ADVCOMMS' = "Microsoft 365 Advanced Communications" 'TEAMS_AR_DOD' = "Microsoft Teams for DOD (AR)" 'TEAMS_AR_GCCHIGH' = "Microsoft Teams for GCCHigh (AR)" 'TEAMS_COMMERCIAL_TRIAL' = "Microsoft Teams Commercial Cloud" 'Teams_Ess' = "Microsoft Teams Essentials" 'TEAMS_ESSENTIALS_AAD' = "Microsoft Teams Essentials (AAD Identity)" 'TEAMS_EXPLORATORY' = "Microsoft Teams Exploratory" 'TEAMS_FREE' = "Microsoft Teams (Free)" 'TEAMS_FREE_SERVICE' = "TEAMS FREE SERVICE" 'TEAMS_GOV' = "Microsoft Teams for Government" 'Teams_Room_Basic' = "Teams Room Basic" 'Teams_Room_Pro' = "Teams Room Pro" 'Teams_Room_Standard' = "Teams Room Standard" 'TEAMS1' = "Microsoft Teams" 'TeamsEss' = "Microsoft Teams Essentials" 'TEAMSMULTIGEO' = "Teams Multi-Geo" 'TEAMSPRO_CUST' = "Microsoft Teams Premium Personalized" 'TEAMSPRO_MGMT' = "Microsoft Teams Premium Intelligent" 'TEAMSPRO_PROTECTION' = "Microsoft Teams Premium Secure" 'TEAMSPRO_VIRTUALAPPT' = "Microsoft Teams Premium Virtual Appointment" 'TEAMSPRO_WEBINAR' = "Microsoft Teams Premium Webinar" 'THREAT_INTELLIGENCE' = "Microsoft Defender for Office 365 (Plan 2)" 'THREAT_INTELLIGENCE_APP' = "Defender Threat Intelligence" 'THREAT_INTELLIGENCE_GOV' = "Microsoft Defender for Office 365 (Plan 2) GCC" 'TOPIC_EXPERIENCES' = "Viva Topics" 'TVM_PREMIUM_1' = "Microsoft Defender Vulnerability Management" 'TVM_Premium_Add_on' = "Microsoft Defender Vulnerability Management Add-on" 'TVM_Premium_Standalone' = "Microsoft Defender Vulnerability Management" 'UNIVERSAL_PRINT' = "Universal Print" 'UNIVERSAL_PRINT_01' = "Universal Print" 'UNIVERSAL_PRINT_NO_SEEDING' = "Universal Print Without Seeding" 'VIRTUAL_AGENT_BASE' = "Power Virtual Agent" 'VIRTUAL_AGENT_USL' = "Power Virtual Agent User License" 'Virtualization Rights for Windows 10 (E3/E5+VDA)' = "Windows 10 Enterprise (New)" 'Virtualization Rights for Windows 10' = "Windows 10/11 Enterprise" 'Virtualization Rights for Windows 10 (E3/E5+VDA)' = "Windows 10/11 Enterprise" 'VISIO_CLIENT_SUBSCRIPTION' = "Visio Desktop App" 'VISIO_CLIENT_SUBSCRIPTION_GOV' = "VISIO DESKTOP APP FOR Government" 'VISIO_PLAN1_DEPT' = "Visio Plan 1" 'VISIO_PLAN2_DEPT' = "Visio Plan 2" 'VISIOCLIENT' = "Visio Online Plan 2" 'VISIOCLIENT_FACULTY' = "Visio Plan 2 for Faculty" 'VISIOCLIENT_GOV' = "Visio Plan 2 for GCC" 'VISIOONLINE' = "Visio web app" 'VISIOONLINE_GOV' = "VISIO WEB APP FOR GOVERNMENT" 'VISIOONLINE_PLAN1' = "Visio Online Plan 1" 'VIVA' = "Microsoft Viva Suite" 'Viva_Goals_Premium' = "Viva Goals" 'VIVA_LEARNING_PREMIUM' = "Viva Learning" 'VIVA_LEARNING_SEEDED' = "Viva Learning Seeded" 'VIVAENGAGE_COMMUNITIES_AND_COMMUNICATIONS' = "Viva Engage Communities and Communications" 'VIVAENGAGE_CORE' = "Viva Engage Core" 'VIVAENGAGE_KNOWLEDGE' = "Viva Engage Knowledge" 'WACONEDRIVEENTERPRISE' = "OneDrive for Business (Plan 2)" 'WACONEDRIVESTANDARD' = "OneDrive for Business (Plan 1)" 'WHITEBOARD_FIRSTLINE1' = "Whiteboard (Firstline)" 'WHITEBOARD_PLAN1' = "Whiteboard (Plan 1)" 'WHITEBOARD_PLAN2' = "Whiteboard (Plan 2)" 'WHITEBOARD_PLAN3' = "Whiteboard (Plan 3)" 'WIN_DEF_ATP' = "Microsoft Defender for Endpoint" 'WIN_ENT_E5' = "Windows 10/11 Enterprise E5 (Original)" 'WIN10_ENT_A3_FAC' = "Windows 10/11 Enterprise A3 for faculty" 'WIN10_ENT_A3_STU' = "Windows 10/11 Enterprise A3 for students" 'WIN10_ENT_A5_FAC' = "Windows 10/11 Enterprise A5 for faculty" 'WIN10_ENT_LOC_F1' = "Windows 10 Enterprise E3 (Local Only)" 'WIN10_PRO_ENT_SUB' = "Windows 10/11 Enterprise E3" 'WIN10_VDA_E3' = "Windows 10/11 Enterprise E3" 'WIN10_VDA_E5' = "Windows 10/11 Enterprise E5" 'WINBIZ' = "Windows 10/11 Business" 'WINDEFATP' = "Microsoft Defender for Endpoint" 'Windows Autopatch' = "Windows Autopatch" 'Windows Store for Business EDU Store_faculty' = "Windows Store for Business EDU Store_faculty" 'Windows_365_S_2vCPU_4GB_128GB' = "Windows 365 Shared Use 2 vCPU 4 GB 128 GB" 'Windows_365_S_2vCPU_4GB_256GB' = "Windows 365 Shared Use 2 vCPU 4 GB 256 GB" 'Windows_365_S_2vCPU_4GB_64GB' = "Windows 365 Shared Use 2 vCPU 4 GB 64 GB" 'Windows_365_S_2vCPU_8GB_128GB' = "Windows 365 Shared Use 2 vCPU 8 GB 128 GB" 'Windows_365_S_2vCPU_8GB_256GB' = "Windows 365 Shared Use 2 vCPU 8 GB 256 GB" 'Windows_365_S_4vCPU_16GB_128GB' = "Windows 365 Shared Use 4 vCPU 16 GB 128 GB" 'Windows_365_S_4vCPU_16GB_256GB' = "Windows 365 Shared Use 4 vCPU 16 GB 256 GB" 'Windows_365_S_4vCPU_16GB_512GB' = "Windows 365 Shared Use 4 vCPU 16 GB 512 GB" 'Windows_365_S_8vCPU_32GB_128GB' = "Windows 365 Shared Use 8 vCPU 32 GB 128 GB" 'Windows_365_S_8vCPU_32GB_256GB' = "Windows 365 Shared Use 8 vCPU 32 GB 256 GB" 'Windows_365_S_8vCPU_32GB_512GB' = "Windows 365 Shared Use 8 vCPU 32 GB 512 GB" 'Windows_Autopatch' = "Windows Autopatch" 'WINDOWS_STORE' = "Windows Store for Business" 'WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE' = "Windows Update for Business Deployment Service" 'WINE5_GCC_COMPAT' = "Windows 10/11 Enterprise E5 Commercial (GCC Compatible)" 'WORKPLACE_ANALYTICS' = "Microsoft Workplace Analytics" 'WORKPLACE_ANALYTICS_INSIGHTS_BACKEND' = "Microsoft Viva Insights Backend" 'WORKPLACE_ANALYTICS_INSIGHTS_USER' = "Microsoft Viva Insights" 'WSFB_EDU_FACULTY' = "Windows Store for Business EDU Faculty" 'YAMMER_EDU' = "Yammer for Academic" 'YAMMER_ENTERPRISE' = "YAMMER_ENTERPRISE" 'YAMMER_MIDSIZE' = "YAMMER MIDSIZE" } $SKUO365 = [ordered] @{ 'AAD_PREMIUM' = "AAD_PREMIUM" 'ADALLOM_S_DISCOVERY' = "ADALLOM_S_DISCOVERY" 'ADALLOM_S_O365' = "ADALLOM_S_O365" 'Advanced Communications' = "ADV_COMMS" 'AI Builder Capacity add-on' = "CDSAICAPACITY" 'AI Builder capacity Per App add-on' = "CDSAICAPACITY_PERAPP" 'AI Builder capacity Per User add-on' = "CDSAICAPACITY_PERUSER" 'APP CONNECT' = "SPZA" 'App Connect IW' = "SPZA_IW" 'App governance add-on to Microsoft Defender for Cloud Apps' = "Microsoft_Cloud_App_Security_App_Governance_Add_On" 'Asset Maintenance Add-in' = "D365_AssetforSCM" 'ATP_ENTERPRISE_GOV' = "ATP_ENTERPRISE_GOV" 'AUSTRALIA CALLING PLAN' = "MCOPSTNEAU" 'Azure Active Directory' = "AAD_SMB" 'Azure Active Directory Basic' = "AAD_BASIC" 'Azure Active Directory Basic for EDU' = "AAD_BASIC_EDU" 'Azure Active Directory Basic for Education' = "AAD_BASIC_EDU" 'Azure Active Directory for Education' = "AAD_EDU" 'Azure Active Directory Premium P1' = "AAD_PREMIUM" 'Azure Active Directory Premium P1 for Faculty' = "AAD_PREMIUM_FACULTY" 'Azure Active Directory Premium P2' = "AAD_PREMIUM_P2" 'Azure Active Directory Premium Plan 1' = "AAD_PREMIUM" 'Azure Information Protection Plan 1' = "RIGHTSMANAGEMENT" 'Azure Information Protection Premium P' = "RMS_S_PREMIUM" 'AZURE INFORMATION PROTECTION PREMIUM P1' = "RMS_S_ENTERPRISE" 'Azure Information Protection Premium P1 for GCC' = "RMS_S_PREMIUM_GOV" 'AZURE INFORMATION PROTECTION PREMIUM P2' = "RMS_S_PREMIUM2" 'Azure Information Protection Premium P2 for GCC' = "RMS_S_PREMIUM2_GOV" 'Azure Rights Management' = "RMS_S_ENTERPRISE" 'AZURE RIGHTS MANAGEMENT PREMIUM FOR GOVERNMENT' = "RMS_S_PREMIUM_GOV" 'BI_AZURE_P_2_GOV' = "BI_AZURE_P_2_GOV" 'BI_AZURE_P0' = "BI_AZURE_P0" 'BPOS_S_TODO_1' = "BPOS_S_TODO_1" 'BPOS_S_TODO_2' = "BPOS_S_TODO_2" 'BPOS_S_TODO_3' = "BPOS_S_TODO_3" 'Business Apps (free)' = "SMB_APPS" 'CDS Per app baseline access' = "CDS_PER_APP_IWTRIAL" 'CDS PowerApps per app plan' = "CDS_PER_APP" 'CDS PowerApps Portals page view capacity add-on' = "CDS_POWERAPPS_PORTALS_PAGEVIEW" 'CDS PowerApps Portals page view capacity add-on for GCC' = "CDS_POWERAPPS_PORTALS_PAGEVIEW_GCC" 'CDS_O365_P3_GCC' = "CDS_O365_P3_GCC" 'CLOUD APP SECURITY DISCOVERY' = "ADALLOM_S_DISCOVERY" 'Common Data Service' = "DYN365_CDS_FORMS_PRO" 'Common Data Service - DEV VIRAL' = "DYN365_CDS_DEV_VIRAL" 'Common Data Service - O365 F1' = "DYN365_CDS_O365_F1_GCC" 'Common Data Service - O365 P1' = "DYN365_CDS_O365_P1" 'Common Data Service - O365 P1 GCC' = "DYN365_CDS_O365_P1_GCC" 'Common Data Service - O365 P2' = "DYN365_CDS_O365_P2" 'COMMON DATA SERVICE - O365 P2 GCC' = "DYN365_CDS_O365_P2_GCC" 'Common Data Service - O365 P3' = "DYN365_CDS_O365_P3" 'Common Data Service - P2' = "DYN365_CDS_P2" 'Common Data Service - VIRAL' = "DYN365_CDS_VIRAL" 'Common Data Service Attended RPA' = "CDS_ATTENDED_RPA" 'Common Data Service Database Capacity' = "CDS_DB_CAPACITY" 'Common Data Service Database Capacity for Government' = "CDS_DB_CAPACITY_GOV" 'Common Data Service for Apps Database Capacity' = "CDS_DB_CAPACITY" 'Common Data Service for Apps Database Capacity for Government' = "CDS_DB_CAPACITY_GOV" 'Common Data Service for Apps File Capacity' = "CDS_FILE_CAPACITY" 'Common Data Service for Apps Log Capacity' = "CDS_LOG_CAPACITY" 'Common Data Service for CCI Bots' = "DYN365_CDS_CCI_BOTS" 'Common Data Service for Customer Insights' = "CDS_CUSTOMER_INSIGHTS" 'Common Data Service for Customer Insights Trial' = "CDS_CUSTOMER_INSIGHTS_TRIAL" 'Common Data Service for Dynamics 365 Finance' = "DYN365_CDS_FINANCE" 'COMMON DATA SERVICE FOR DYNAMICS 365 SUPPLY CHAIN MANAGEMENT' = "DYN365_CDS_SUPPLYCHAINMANAGEMENT" 'Common data service for Flow per business process plan' = "CDS_Flow_Business_Process" 'Common Data Service for Government' = "DYN365_CDS_P2_GOV" 'Common Data Service for Project' = "DYN365_CDS_PROJECT" 'COMMON DATA SERVICE FOR PROJECT P1' = "DYN365_CDS_FOR_PROJECT_P1" 'Common Data Service for Remote Assist' = "CDS_REMOTE_ASSIST" 'Common Data Service for SharePoint Syntex' = "CDS_O365_E5_KM" 'Common Data Service for Teams' = "CDS_O365_P2" 'Common Data Service for Teams_F1' = "CDS_O365_F1" 'Common Data Service for Teams_F1 GCC' = "CDS_O365_F1_GCC" 'COMMON DATA SERVICE FOR TEAMS_P1' = "CDS_O365_P1" 'Common Data Service for Teams_P1 GCC' = "CDS_O365_P1_GCC" 'Common Data Service for Teams_P2' = "CDS_O365_P2" 'COMMON DATA SERVICE FOR TEAMS_P2 GCC' = "CDS_O365_P2_GCC" 'Common Data Service for Teams_P3' = "CDS_O365_P3" 'Common Data Service for Virtual Agent Base' = "CDS_VIRTUAL_AGENT_BASE" 'Common Data Service Log Capacity' = "CDS_LOG_CAPACITY" 'Common Data Service Power Apps Portals Login Capacity' = "CDS_POWERAPPS_PORTALS_LOGIN" 'Common Data Service Power Apps Portals Login Capacity for GCC' = "CDS_POWERAPPS_PORTALS_LOGIN_GCC" 'Common Data Service Unattended RPA' = "CDS_UNATTENDED_RPA" 'Communications Credits' = "MCOPSTNC" 'COMMUNICATIONS_DLP' = "COMMUNICATIONS_DLP" 'Compliance Manager Premium Assessment Add-On' = "CMPA_addon" 'Compliance Manager Premium Assessment Add-On for GCC' = "CMPA_addon_GCC" 'Content_Explorer' = "Content_Explorer" 'ContentExplorer_Standard' = "ContentExplorer_Standard" 'CRM Hybrid Connector' = "CRM_HYBRIDCONNECTOR" 'Customer Lockbox' = "LOCKBOX_ENTERPRISE" 'Customer Lockbox for Government' = "LOCKBOX_ENTERPRISE_GOV" 'Customer Voice for Dynamics 365 vTrial' = "CUSTOMER_VOICE_DYN365_VIRAL_TRIAL" 'CUSTOMER_KEY' = "CUSTOMER_KEY" 'Data Classification in Microsoft 365' = "MIP_S_Exchange" 'Data Classification in Microsoft 365 - Company Level' = "MIP_S_EXCHANGE_CO" 'Data Loss Prevention' = "BPOS_S_DlpAddOn" 'Dataverse for Cust Insights�BASE' = "CDS_CUSTOMER_INSIGHTS_BASE" 'Dataverse for Customer Insights�BASE' = "CDS_CUSTOMER_INSIGHTS_BASE" 'Dataverse for PAD' = "DATAVERSE_FOR_POWERAUTOMATE_DESKTOP" 'Dataverse for Power Apps per app' = "DATAVERSE_POWERAPPS_PER_APP_NEW" 'Defender Threat Intelligence' = "Defender_Threat_Intelligence" 'DOMESTIC AND INTERNATIONAL CALLING PLAN' = "MCOPSTN2" 'Domestic Calling for Government' = "MCOPSTN1_GOV" 'DOMESTIC CALLING PLAN' = "MCOPSTN1" 'DYN365_CDS_O365_F1' = "DYN365_CDS_O365_F1" 'DYN365_CDS_O365_P3_GCC' = "DYN365_CDS_O365_P3_GCC" 'Dynamics 365 - Additional Database Storage (Qualified Offer)' = "CRMSTORAGE" 'Dynamics 365 - Additional Non-Production Instance (Qualified Offer)' = "CRMTESTINSTANCE" 'Dynamics 365 - Additional Production Instance (Qualified Offer)' = "CRMINSTANCE" 'Dynamics 365 AI for Customer Service Trial' = "DYN365_AI_SERVICE_INSIGHTS" 'Dynamics 365 AI for Customer Service Virtual Agents Viral' = "CCIBOTS_PRIVPREV_VIRAL" 'Dynamics 365 AI for Market Insights - Free' = "SOCIAL_ENGAGEMENT_APP_USER" 'Dynamics 365 AI for Market Insights (Preview)' = "SOCIAL_ENGAGEMENT_APP_USER" 'Dynamics 365 AI for Sales (Embedded)' = "DYN365_SALES_INSIGHTS" 'Dynamics 365 Asset Management Addl Assets' = "DYN365_ASSETMANAGEMENT" 'Dynamics 365 Business Central Additional Environment Addon' = "DYN365_BUSCENTRAL_ADD_ENV_ADDON" 'Dynamics 365 Business Central Database Capacity' = "DYN365_BUSCENTRAL_DB_CAPACITY" 'Dynamics 365 Business Central Essentials' = "DYN365_BUSCENTRAL_ESSENTIAL" 'Dynamics 365 Business Central External Accountant' = "DYN365_FINANCIALS_ACCOUNTANT_SKU" 'Dynamics 365 Business Central for IWs' = "PROJECT_MADEIRA_PREVIEW_IW_SKU" 'Dynamics 365 Business Central Premium' = "DYN365_BUSCENTRAL_PREMIUM" 'Dynamics 365 Business Central Team Members' = "DYN365_BUSCENTRAL_TEAM_MEMBER" 'Dynamics 365 Commerce Trial' = "DYN365_RETAIL_TRIAL" 'Dynamics 365 Customer Engagement Plan' = "DYN365_ENTERPRISE_PLAN1" 'Dynamics 365 Customer Insights' = "DYN365_CUSTOMER_INSIGHTS_BASE" 'Dynamics 365 Customer Insights Attach' = "DYN365_CUSTOMER_INSIGHTS_ATTACH" 'Dynamics 365 Customer Insights Engagement Insights' = "DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE" 'Dynamics 365 Customer Insights Engagement Insights Viral' = "DYN365_CUSTOMER_INSIGHTS_ENGAGEMENT_INSIGHTS_BASE_TRIAL" 'Dynamics 365 Customer Insights Standalone' = "DYN365_CUSTOMER_INSIGHTS_BASE" 'Dynamics 365 Customer Insights Viral Plan' = "DYN365_CUSTOMER_INSIGHTS_VIRAL" 'Dynamics 365 Customer Insights vTrial' = "DYN365_CUSTOMER_INSIGHTS_VIRAL" 'Dynamics 365 Customer Service Chat Application Integration' = "DYN365_CS_CHAT_FPA" 'Dynamics 365 Customer Service Digital Messaging add-on' = "DYN365_CS_MESSAGING_TPS" 'Dynamics 365 Customer Service Digital Messaging vTrial' = "DYN365_CS_MESSAGING_VIRAL_TRIAL" 'Dynamics 365 Customer Service Enterprise Admin' = "Dynamics_365_Customer_Service_Enterprise_admin_trial" 'Dynamics 365 Customer Service Enterprise Viral Trial' = "Dynamics_365_Customer_Service_Enterprise_viral_trial" 'Dynamics 365 Customer Service Enterprise vTrial' = "DYN365_CS_ENTERPRISE_VIRAL_TRIAL" 'Dynamics 365 Customer Service Insights for CE Plan' = "D365_CSI_EMBED_CE" 'Dynamics 365 Customer Service Insights for CS Enterprise' = "D365_CSI_EMBED_CSEnterprise" 'Dynamics 365 Customer Service Insights Trial' = "DYN365_AI_SERVICE_INSIGHTS" 'Dynamics 365 Customer Service Insights vTrial' = "DYNB365_CSI_VIRAL_TRIAL" 'Dynamics 365 Customer Service Professional' = "DYN365_CUSTOMER_SERVICE_PRO" 'Dynamics 365 Customer Service Voice vTrial' = "DYN365_CS_VOICE_VIRAL_TRIAL" 'Dynamics 365 Customer Voice' = "DYN365_CUSTOMER_VOICE_BASE" 'Dynamics 365 Customer Voice Additional Responses' = "DYN365_CUSTOMER_VOICE_ADDON" 'Dynamics 365 Customer Voice Base Plan' = "Customer_Voice_Base" 'Dynamics 365 Customer Voice Trial' = "FORMS_PRO" 'Dynamics 365 Customer Voice USL' = "Forms_Pro_USL" 'Dynamics 365 Enterprise Edition - Additional Portal (Qualified Offer)' = "CRM_ONLINE_PORTAL" 'Dynamics 365 Field Service Enterprise vTrial' = "DYN365_FS_ENTERPRISE_VIRAL_TRIAL" 'Dynamics 365 Field Service Viral Trial' = "Dynamics_365_Field_Service_Enterprise_viral_trial" 'Dynamics 365 Field Service, Enterprise Edition - Resource Scheduling Optimization' = "CRM_AUTO_ROUTING_ADDON" 'Dynamics 365 Finance' = "DYN365_FINANCE" 'Dynamics 365 for Business Central Essentials' = "DYN365_FINANCIALS_BUSINESS" 'Dynamics 365 for Case Management' = "DYN365_ENTERPRISE_CASE_MANAGEMENT" 'Dynamics 365 for Case Management Enterprise Edition' = "DYN365_ENTERPRISE_CASE_MANAGEMENT" 'Dynamics 365 for Customer Service' = "DYN365_ENTERPRISE_CUSTOMER_SERVICE" 'Dynamics 365 for Customer Service Chat' = "DYN365_CS_CHAT" 'Dynamics 365 for Customer Service Enterprise Attach' = "D365_CUSTOMER_SERVICE_ENT_ATTACH" 'Dynamics 365 for Customer Service Enterprise Attach to Qualifying Dynamics 365 Base Offer A' = "D365_CUSTOMER_SERVICE_ENT_ATTACH" 'Dynamics 365 for Customer Service Enterprise Edition' = "DYN365_ENTERPRISE_CUSTOMER_SERVICE" 'Dynamics 365 for Customer Service Pro' = "DYN365_CUSTOMER_SERVICE_PRO" 'Dynamics 365 for Customer Service Voice Add-in' = "DYN365_CS_VOICE" 'Dynamics 365 for Field Service' = "DYN365_ENTERPRISE_FIELD_SERVICE" 'Dynamics 365 for Field Service Attach' = "D365_FIELD_SERVICE_ATTACH" 'Dynamics 365 for Field Service Attach to Qualifying Dynamics 365 Base Offer' = "D365_FIELD_SERVICE_ATTACH" 'Dynamics 365 for Field Service Enterprise Edition' = "DYN365_ENTERPRISE_FIELD_SERVICE" 'Dynamics 365 for Finance and Operations Enterprise edition - Regulatory Service' = "DYN365_REGULATORY_SERVICE" 'DYNAMICS 365 FOR FINANCIALS' = "POWERAPPS_DYN_APPS" 'Dynamics 365 for Financials Business Edition' = "DYN365_FINANCIALS_BUSINESS_SKU" 'Dynamics 365 for HCM Trial' = "Dynamics_365_for_HCM_Trial" 'Dynamics 365 for Marketing' = "DYN365_MARKETING_APP" 'Dynamics 365 for Marketing 50K Addnl Contacts' = "DYN365_MARKETING_50K_CONTACT_ADDON" 'Dynamics 365 for Marketing Additional Application' = "DYN365_MARKETING_APPLICATION_ADDON" 'Dynamics 365 for Marketing Additional Non-Prod Application' = "DYN365_MARKETING_SANDBOX_APPLICATION_ADDON" 'Dynamics 365 for Marketing Addnl Contacts Tier 3' = "DYN365_MARKETING_CONTACT_ADDON_T3" 'Dynamics 365 for Marketing Addnl Contacts Tier 5' = "DYN365_MARKETING_CONTACT_ADDON_T5" 'Dynamics 365 for Marketing Attach' = "DYN365_MARKETING_APP_ATTACH" 'Dynamics 365 for Marketing Business Edition' = "DYN365_BUSINESS_MARKETING" 'Dynamics 365 for Marketing MSE User' = "DYN365_MARKETING_MSE_USER" 'Dynamics 365 for Marketing USL' = "D365_MARKETING_USER" 'Dynamics 365 for Operations Devices' = "Dynamics_365_for_OperationsDevices" 'Dynamics 365 for Operations Enterprise Edition - Sandbox Tier 4:Standard Performance Testing' = "Dynamics_365_for_Operations_Sandbox_Tier4" 'Dynamics 365 for Operations non-production multi-box instance for standard acceptance testing (Tier 2)' = "Dynamics_365_for_Operations_Sandbox_Tier2" 'DYNAMICS 365 FOR OPERATIONS TEAM MEMBERS' = "DYNAMICS_365_FOR_OPERATIONS_TEAM_MEMBERS" 'DYNAMICS 365 FOR RETAIL' = "Dynamics_365_for_Retail" 'Dynamics 365 for Retail Device' = "DYN365_RETAIL_DEVICE" 'DYNAMICS 365 FOR RETAIL TEAM MEMBERS' = "Dynamics_365_for_Retail_Team_members" 'Dynamics 365 for Retail Trial' = "DYN365_RETAIL_TRIAL" 'DYNAMICS 365 FOR SALES' = "DYN365_ENTERPRISE_SALES" 'Dynamics 365 for Sales and Customer Service Enterprise Edition' = "DYN365_ENTERPRISE_SALES_CUSTOMERSERVICE" 'Dynamics 365 for Sales Enterprise Attach' = "D365_SALES_ENT_ATTACH" 'Dynamics 365 for Sales Enterprise Edition' = "DYN365_ENTERPRISE_SALES" 'Dynamics 365 for Sales Pro Attach' = "D365_SALES_PRO_ATTACH" 'Dynamics 365 For Sales Professional' = "D365_SALES_PRO" 'Dynamics 365 For Sales Professional Trial' = "D365_SALES_PRO_IW" 'Dynamics 365 for Supply Chain Management' = "DYN365_SCM" 'Dynamics 365 for Talent' = "SKU_Dynamics_365_for_HCM_Trial" 'DYNAMICS 365 FOR TALENT - ATTRACT EXPERIENCE TEAM MEMBER' = "DYN365_Enterprise_Talent_Attract_TeamMember" 'DYNAMICS 365 FOR TALENT - ONBOARD EXPERIENCE' = "DYN365_Enterprise_Talent_Onboard_TeamMember" 'DYNAMICS 365 FOR TALENT TEAM MEMBERS' = "Dynamics_365_for_Talent_Team_members" 'Dynamics 365 for Talent: Attract' = "Dynamics_365_Hiring_Free_PLAN" 'Dynamics 365 for Talent: Onboard' = "Dynamics_365_Onboarding_Free_PLAN" 'Dynamics 365 for Team Members' = "DYN365_FINANCIALS_TEAM_MEMBERS" 'Dynamics 365 for Team Members Enterprise Edition' = "DYN365_ENTERPRISE_TEAM_MEMBERS" 'DYNAMICS 365 FOR_OPERATIONS' = "Dynamics_365_for_Operations" 'Dynamics 365 Guides' = "GUIDES_USER" 'DYNAMICS 365 HIRING FREE PLAN' = "DYNAMICS_365_HIRING_FREE_PLAN" 'Dynamics 365 Hybrid Connector' = "CRM_HYBRIDCONNECTOR" 'Dynamics 365 Marketing' = "DYN365_BUSINESS_Marketing" 'Dynamics 365 Marketing Sandbox Application AddOn' = "DYN365_MARKETING_SANDBOX_APPLICATION_ADDON" 'Dynamics 365 Operations - Device' = "Dynamics_365_for_Operations_Devices" 'Dynamics 365 Operations - Sandbox Tier 2:Standard Acceptance Testing' = "Dynamics_365_for_Operations_Sandbox_Tier2_SKU" 'Dynamics 365 Operations - Sandbox Tier 4:Standard Performance Testing' = "Dynamics_365_for_Operations_Sandbox_Tier4_SKU" 'Dynamics 365 Operations Trial Environment' = "ERP_TRIAL_INSTANCE" 'Dynamics 365 P1' = "DYN365_ENTERPRISE_P1" 'Dynamics 365 P1 Tria for Information Workers' = "DYN365_ENTERPRISE_P1_IW" 'DYNAMICS 365 P1 TRIAL FOR INFORMATION WORKERS' = "DYN365_ENTERPRISE_P1_IW" 'Dynamics 365 Project Operations' = "D365_ProjectOperations" 'Dynamics 365 Project Operations CDS' = "D365_ProjectOperationsCDS" 'Dynamics 365 Regulatory Service - Enterprise Edition Trial' = "DYN365_REGULATORY_SERVICE" 'Dynamics 365 Remote Assist' = "MICROSOFT_REMOTE_ASSIST" 'Dynamics 365 Remote Assist HoloLens' = "MICROSOFT_REMOTE_ASSIST_HOLOLENS" 'Dynamics 365 Sales Enterprise Attach to Qualifying Dynamics 365 Base Offer' = "D365_SALES_ENT_ATTACH" 'Dynamics 365 Sales Enterprise vTrial' = "DYN365_SALES_ENTERPRISE_VIRAL_TRIAL" 'Dynamics 365 Sales Insights vTrial' = "DYN365_SALES_INSIGHTS_VIRAL_TRIAL" 'Dynamics 365 Sales Premium' = "DYN365_SALES_PREMIUM" 'Dynamics 365 Sales Premium Viral Trial' = "Dynamics_365_Sales_Premium_Viral_Trial" 'Dynamics 365 Sales Professional Attach to Qualifying Dynamics 365 Base Offer' = "D365_SALES_PRO_ATTACH" 'Dynamics 365 Sales, Field Service and Customer Service Partner Sandbox' = "Dynamics_365_Sales_Field_Service_and_Customer_Service_Partner_Sandbox" 'Dynamics 365 Talent: Attract' = "Dynamics_365_Hiring_SKU" 'Dynamics 365 Talent: Onboard' = "DYNAMICS_365_ONBOARDING_SKU" 'Dynamics 365 Team Members' = "DYN365_TEAM_MEMBERS" 'Dynamics 365 UNF OPS Plan ENT Edition' = "Dynamics_365_for_Operations" 'Dynamics Customer Voice Add-On' = "CUSTOMER_VOICE_ADDON" 'Education Analytics' = "EducationAnalyticsP1" 'Enterprise Mobility + Security A3 for Faculty' = "EMS_EDU_FACULTY" 'Enterprise Mobility + Security E3' = "EMS" 'Enterprise Mobility + Security E5' = "EMSPREMIUM" 'Enterprise Mobility + Security G3 GCC' = "EMS_GOV" 'Enterprise Mobility + Security G5 GCC' = "EMSPREMIUM_GOV" 'EQUIVIO_ANALYTICS_GOV' = "EQUIVIO_ANALYTICS_GOV" 'Exchange Enterprise CAL Services (EOP DLP)' = "EOP_ENTERPRISE_PREMIUM" 'EXCHANGE ESSENTIALS' = "EXCHANGE_S_ESSENTIALS" 'Exchange Foundation' = "EXCHANGE_S_FOUNDATION" 'Exchange Foundation for Government' = "EXCHANGE_S_FOUNDATION_GOV" 'Exchange Online (Kiosk) for Government' = "EXCHANGE_S_DESKLESS_GOV" 'EXCHANGE ONLINE (P1)' = "EXCHANGE_L_STANDARD" 'Exchange Online (Plan 1)' = "EXCHANGESTANDARD" 'Exchange Online (Plan 1) for Alumni with Yammer' = "EXCHANGESTANDARD_ALUMNI" 'Exchange Online (Plan 1) for GCC' = "EXCHANGESTANDARD_GOV" 'Exchange Online (Plan 1) for Government' = "EXCHANGE_S_STANDARD_GOV" 'Exchange Online (Plan 1) for Students' = "EXCHANGESTANDARD_STUDENT" 'Exchange Online (Plan 2)' = "EXCHANGEENTERPRISE" 'Exchange Online (Plan 2) for Faculty' = "EXCHANGEENTERPRISE_FACULTY" 'Exchange Online (Plan 2) for Government' = "EXCHANGE_S_ENTERPRISE_GOV" 'Exchange Online Archiving' = "EXCHANGE_S_ARCHIVE_ADDON" 'Exchange Online Archiving for Exchange Online' = "EXCHANGEARCHIVE_ADDON" 'Exchange Online Archiving for Exchange Server' = "EXCHANGEARCHIVE" 'Exchange Online Essentials' = "EXCHANGE_S_ESSENTIALS" 'Exchange Online Essentials (ExO P1 Based)' = "EXCHANGEESSENTIALS" 'Exchange Online Kiosk' = "EXCHANGEDESKLESS" 'Exchange Online Multi-Geo' = "EXCHANGEONLINE_MULTIGEO" 'EXCHANGE ONLINE PLAN' = "EXCHANGE_S_STANDARD_MIDMARKET" 'Exchange Online POP' = "EXCHANGETELCO" 'Exchange Online Protection' = "EOP_ENTERPRISE" 'EXCHANGE PLAN 2G' = "EXCHANGE_S_ENTERPRISE_GOV" 'EXCHANGE_ANALYTICS_GOV' = "EXCHANGE_ANALYTICS_GOV" 'EXCHANGE_S_DESKLESS' = "EXCHANGE_S_DESKLESS" 'EXCHANGE_S_ENTERPRISE_GOV' = "EXCHANGE_S_ENTERPRISE_GOV" 'EXCHANGE_S_FOUNDATION' = "EXCHANGE_S_FOUNDATION" 'EXCHANGE_S_FOUNDATION_GOV' = "EXCHANGE_S_FOUNDATION_GOV" 'Field Service � Automated Routing Engine Add-On' = "CRM_AUTO_ROUTING_ENGINE_ADDON" 'Flow for CCI Bots' = "FLOW_CCI_BOTS" 'Flow for Developer' = "FLOW_DEV_VIRAL" 'FLOW FOR DYNAMICS 36' = "FLOW_DYN_P2" 'Flow for Dynamics 365' = "FLOW_DYN_APPS" 'FLOW FOR OFFICE 365' = "FLOW_O365_P1" 'Flow for Project' = "FLOW_FOR_PROJECT" 'Flow Free' = "FLOW_P2_VIRAL" 'Flow P2 Viral' = "FLOW_P2_VIRAL_REAL" 'Flow per app baseline access' = "Flow_Per_APP_IWTRIAL" 'Flow per business process plan' = "FLOW_BUSINESS_PROCESS" 'Flow per user plan' = "FLOW_PER_USER" 'FLOW_O365_P3_GOV' = "FLOW_O365_P3_GOV" 'Forms for Government (Plan E1)' = "FORMS_GOV_E1" 'FORMS FOR GOVERNMENT (PLAN E3)' = "FORMS_GOV_E3" 'Forms for Government (Plan F1)' = "FORMS_GOV_F1" 'FORMS_GOV_E5' = "FORMS_GOV_E5" 'Graph Connectors Search with Index' = "GRAPH_CONNECTORS_SEARCH_INDEX" 'Graph Connectors Search with Index (Microsoft Viva Topics)' = "GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP" 'Graph Connectors Search with Index (Viva Topics)' = "GRAPH_CONNECTORS_SEARCH_INDEX_TOPICEXP" 'INFO_GOVERNANCE' = "INFO_GOVERNANCE" 'Information Barriers' = "INFORMATION_BARRIERS" 'Information Protection and Governance Analytics � Premium' = "Content_Explorer" 'Information Protection and Governance Analytics - Premium' = "Content_Explorer" 'Information Protection and Governance Analytics - Premium)' = "Content_Explorer" 'Information Protection and Governance Analytics � Standard' = "ContentExplorer_Standard" 'Information Protection and Governance Analytics - Standard' = "ContentExplorer_Standard" 'Information Protection and Governance Analytics -Premium' = "Content_Explorer" 'Information Protection for Office 365 - Premium' = "MIP_S_CLP2" 'Information Protection for Office 365 - Standard' = "MIP_S_CLP1" 'INFORMATION_BARRIERS' = "INFORMATION_BARRIERS" 'Insights by MyAnalytics' = "MYANALYTICS_P2" 'INSIGHTS BY MYANALYTICS FOR GOVERNMENT' = "MYANALYTICS_P2_GOV" 'Intune' = "INTUNE_A" 'Intune Advanced endpoint analytics' = "Intune_AdvancedEA" 'Intune Endpoint Privilege Management' = "Intune-EPM" 'Intune for Education' = "INTUNE_EDU" 'Intune Plan 2' = "INTUNE_P2" 'INTUNE_A' = "INTUNE_A" 'INTUNE_O365' = "INTUNE_O365" 'IoT Intelligence Add-in Additional Machines' = "D365_IOTFORSCM_ADDITIONAL" 'Iot Intelligence Add-in for D365 Supply Chain Management' = "D365_IOTFORSCM" 'LOCKBOX_ENTERPRISE_GOV' = "LOCKBOX_ENTERPRISE_GOV" 'LOGIC FLOWS' = "POWERFLOWSFREE" 'M365 Communication Compliance' = "MICROSOFT_COMMUNICATION_COMPLIANCE" 'M365_ADVANCED_AUDITING' = "M365_ADVANCED_AUDITING" 'MCO FREE FOR MICROSOFT TEAMS (FREE)' = "MCOFREE" 'MCOEV_GOV' = "MCOEV_GOV" 'MCOIMP' = "MCOIMP" 'MCOMEETADV_GOV' = "MCOMEETADV_GOV" 'MCOPSTN3' = "MCOPSTN3" 'MCOSTANDARD_GOV' = "MCOSTANDARD_GOV" 'MCS - BizApps_Cloud for Sustainability_vTrial' = "MCS_BizApps_Cloud_for_Sustainability_vTrial" 'MDE_SecurityManagement' = "Intune_Defender" 'Meeting Room Managed Services' = "MMR_P1" 'MFA_PREMIUM' = "MFA_PREMIUM" 'Microsoft 365 A1' = "M365EDU_A1" 'Microsoft 365 A3 - Unattended License for students use benefit' = "M365EDU_A3_STUUSEBNFT_RPA1" 'Microsoft 365 A3 for Faculty' = "M365EDU_A3_FACULTY" 'Microsoft 365 A3 for Students' = "M365EDU_A3_STUDENT" 'Microsoft 365 A3 for students use benefit' = "M365EDU_A3_STUUSEBNFT" 'Microsoft 365 A3 Suite features for faculty' = "Microsoft 365 A3 Suite features for faculty" 'Microsoft 365 A5 for Faculty' = "M365EDU_A5_FACULTY" 'Microsoft 365 A5 for Students' = "M365EDU_A5_STUDENT" 'Microsoft 365 A5 for students use benefit' = "M365EDU_A5_STUUSEBNFT" 'Microsoft 365 A5 Suite features for faculty' = "M365_A5_SUITE_COMPONENTS_FACULTY" 'Microsoft 365 A5 without Audio Conferencing for students use benefit' = "M365EDU_A5_NOPSTNCONF_STUUSEBNFT" 'Microsoft 365 Advanced Auditing' = "M365_ADVANCED_AUDITING" 'Microsoft 365 Advanced Communications' = "TEAMS_ADVCOMMS" 'Microsoft 365 Apps for Business' = "SMB_BUSINESS" 'Microsoft 365 Apps for Enterprise' = "OFFICESUBSCRIPTION" 'Microsoft 365 Apps for enterprise (device)' = "OFFICE_PROPLUS_DEVICE1" 'Microsoft 365 Apps for Enterprise (Unattended)' = "OFFICESUBSCRIPTION_unattended" 'Microsoft 365 Apps for enterprise G' = "OFFICESUBSCRIPTION_GOV" 'Microsoft 365 Apps for Faculty' = "OFFICESUBSCRIPTION_FACULTY" 'Microsoft 365 Apps for Students' = "OFFICESUBSCRIPTION_STUDENT" 'Microsoft 365 Audio Conferencing' = "MCOMEETADV" 'Microsoft 365 Audio Conferencing for GCC' = "MCOMEETADV_GOV" 'MICROSOFT 365 AUDIO CONFERENCING FOR GOVERNMENT' = "MCOMEETADV_GOV" 'Microsoft 365 Audio Conferencing Pay-Per-Minute' = "MCOMEETACPEA" 'Microsoft 365 Audio Conferencing Pay-Per-Minute - EA' = "MCOMEETACPEA" 'Microsoft 365 Audit Platform' = "M365_AUDIT_PLATFORM" 'Microsoft 365 Business Basic' = "SMB_BUSINESS_ESSENTIALS" 'Microsoft 365 Business Premium' = "SPB" 'Microsoft 365 Business Standard' = "O365_BUSINESS_PREMIUM" 'Microsoft 365 Business Standard - Prepaid Legacy' = "SMB_BUSINESS_PREMIUM" 'Microsoft 365 Communication Compliance' = "MICROSOFT_COMMUNICATION_COMPLIANCE" 'Microsoft 365 Defender' = "MTP" 'Microsoft 365 Domestic Calling Plan' = "MCOPSTN1" 'MICROSOFT 365 DOMESTIC CALLING PLAN (120 min)' = "MCOPSTN5" 'Microsoft 365 Domestic Calling Plan (120 min) at User Level' = "MCOPSTN8" 'Microsoft 365 Domestic Calling Plan (120 Minutes)' = "MCOPSTN_5" 'Microsoft 365 Domestic Calling Plan for GCC' = "MCOPSTN_1_GOV" 'Microsoft 365 E3' = "SPE_E3" 'Microsoft 365 E3 - Unattended License' = "SPE_E3_RPA1" 'Microsoft 365 E3 (500 seats min)_HUB' = "Microsoft_365_E3" 'Microsoft 365 E3 Extra Features' = "Microsoft_365_E3_Extra_Features" 'Microsoft 365 E3_USGOV_DOD' = "SPE_E3_USGOV_DOD" 'Microsoft 365 E3_USGOV_GCCHIGH' = "SPE_E3_USGOV_GCCHIGH" 'Microsoft 365 E5' = "SPE_E5" 'Microsoft 365 E5 (500 seats min)_HUB' = "Microsoft_365_E5" 'Microsoft 365 E5 Compliance' = "INFORMATION_PROTECTION_COMPLIANCE" 'Microsoft 365 E5 Developer (without Windows and Audio Conferencing)' = "DEVELOPERPACK_E5" 'Microsoft 365 E5 Security' = "IDENTITY_THREAT_PROTECTION" 'Microsoft 365 E5 Security for EMS E5' = "IDENTITY_THREAT_PROTECTION_FOR_EMS_E5" 'Microsoft 365 E5 Suite features' = "M365_E5_SUITE_COMPONENTS" 'Microsoft 365 E5 with Calling Minutes' = "SPE_E5_CALLINGMINUTES" 'Microsoft 365 E5 without Audio Conferencing' = "SPE_E5_NOPSTNCONF" 'Microsoft 365 E5 without Audio Conferencing (500 seats min)_HUB' = "Microsoft_365_E5_without_Audio_Conferencing" 'Microsoft 365 F1' = "M365_F1_COMM" 'Microsoft 365 F3' = "SPE_F1" 'Microsoft 365 F3 GCC' = "M365_F1_GOV" 'Microsoft 365 F5 Compliance Add-on' = "SPE_F5_COMP" 'Microsoft 365 F5 Compliance Add-on AR (DOD)_USGOV_DOD' = "SPE_F5_COMP_AR_D_USGOV_DOD" 'Microsoft 365 F5 Compliance Add-on AR_USGOV_GCCHIGH' = "SPE_F5_COMP_AR_USGOV_GCCHIGH" 'Microsoft 365 F5 Compliance Add-on GCC' = "SPE_F5_COMP_GCC" 'Microsoft 365 F5 Security + Compliance Add-on' = "SPE_F5_SECCOMP" 'Microsoft 365 F5 Security Add-on' = "SPE_F5_SEC" 'Microsoft 365 G3 GCC' = "M365_G3_GOV" 'Microsoft 365 GCC G5' = "M365_G5_GCC" 'Microsoft 365 Lighthouse' = "Microsoft365_Lighthouse" 'Microsoft 365 Lighthouse (Plan 1)' = "M365_LIGHTHOUSE_CUSTOMER_PLAN1" 'Microsoft 365 Lighthouse (Plan 2)' = "M365_LIGHTHOUSE_PARTNER_PLAN1" 'Microsoft 365 Phone Standard Resource Account' = "MCOEV_VIRTUALUSER" 'Microsoft 365 Phone Standard Resource Account for Government' = "MCOEV_VIRTUALUSER_GOV" 'MICROSOFT 365 PHONE SYSTE' = "MCOEV" 'Microsoft 365 Phone System' = "MCOEV" 'Microsoft 365 Phone System for Government' = "MCOEV_GOV" 'Microsoft 365 Security and Compliance for Firstline Workers' = "M365_SECURITY_COMPLIANCE_FOR_FLW" 'Microsoft Application Protection and Governance (A)' = "MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_A" 'Microsoft Application Protection and Governance (D)' = "MICROSOFT_APPLICATION_PROTECTION_AND_GOVERNANCE_D" 'MICROSOFT AZURE ACTIVE DIRECTORY BASIC' = "AAD_BASIC" 'MICROSOFT AZURE ACTIVE DIRECTORY RIGHTS' = "RMS_S_PREMIUM" 'Microsoft Azure Multi-Factor Authentication' = "MFA_STANDALONE" 'Microsoft Azure Rights Management Service' = "RMS_S_BASIC" 'Microsoft Bookings' = "MICROSOFTBOOKINGS" 'Microsoft Business Center' = "MICROSOFT_BUSINESS_CENTER" 'Microsoft Cloud App Security' = "ADALLOM_STANDALONE" 'Microsoft Cloud for Sustainability vTrial' = "Microsoft_Cloud_for_Sustainability_vTrial" 'Microsoft Communications Compliance' = "COMMUNICATIONS_COMPLIANCE" 'Microsoft Communications DLP' = "COMMUNICATIONS_DLP" 'Microsoft Customer Key' = "CUSTOMER_KEY" 'Microsoft Data Investigations' = "DATA_INVESTIGATIONS" 'Microsoft Defender for Business' = "MDE_SMB" 'Microsoft Defender for Cloud Apps' = "ADALLOM_S_STANDALONE" 'Microsoft Defender for Cloud Apps Discovery' = "ADALLOM_S_DISCOVERY" 'Microsoft Defender for Cloud Apps for DOD' = "ADALLOM_S_STANDALONE_DOD" 'Microsoft Defender for Endpoint' = "WIN_DEF_ATP" 'Microsoft Defender for Endpoint P1' = "DEFENDER_ENDPOINT_P1" 'Microsoft Defender for Endpoint P1 for EDU' = "DEFENDER_ENDPOINT_P1_EDU" 'Microsoft Defender for Endpoint P2_XPLAT' = "MDATP_XPLAT" 'Microsoft Defender for Endpoint Plan 1' = "MDE_LITE" 'Microsoft Defender for Endpoint Server' = "MDATP_Server" 'Microsoft Defender for Identity' = "ATA" 'Microsoft Defender for Office 365 (Plan 1)' = "ATP_ENTERPRISE" 'Microsoft Defender for Office 365 (Plan 1) Faculty' = "ATP_ENTERPRISE_FACULTY" 'Microsoft Defender for Office 365 (Plan 1) for Government' = "ATP_ENTERPRISE_GOV" 'Microsoft Defender for Office 365 (Plan 1) GCC' = "ATP_ENTERPRISE_GOV" 'Microsoft Defender for Office 365 (Plan 2)' = "THREAT_INTELLIGENCE" 'Microsoft Defender for Office 365 (Plan 2) for Government' = "THREAT_INTELLIGENCE_GOV" 'Microsoft Defender for Office 365 (Plan 2) GCC' = "THREAT_INTELLIGENCE_GOV" 'Microsoft Defender Vulnerability Management' = "TVM_Premium_Standalone" 'Microsoft Defender Vulnerability Management Add-on' = "TVM_Premium_Add_on" 'Microsoft Dynamics 365 Customer Voice Add-on' = "Forms_Pro_AddOn" 'Microsoft Dynamics 365 Customer Voice for Customer Engagement Plan' = "Forms_Pro_CE" 'Microsoft Dynamics 365 Customer Voice for Customer Insights' = "Forms_Pro_Customer_Insights" 'Microsoft Dynamics 365 Customer Voice for Customer Insights App' = "Customer_Voice_Customer_Insights" 'Microsoft Dynamics 365 Customer Voice for Customer Service Enterprise' = "Forms_Pro_Service" 'Microsoft Dynamics 365 Customer Voice for Field Service' = "Forms_Pro_FS" 'Microsoft Dynamics 365 Customer Voice for Marketing' = "Forms_Pro_Marketing" 'Microsoft Dynamics 365 Customer Voice for Marketing Application' = "Forms_Pro_Marketing_App" 'Microsoft Dynamics 365 Customer Voice for Relationship Sales' = "Forms_Pro_Relationship_Sales" 'Microsoft Dynamics 365 Customer Voice for Sales Enterprise' = "Forms_Pro_SalesEnt" 'Microsoft Dynamics 365 Customer Voice USL' = "Forms_Pro_USL" 'Microsoft Dynamics 365 for Finance' = "D365_Finance" 'Microsoft Dynamics AX7 User Trial' = "AX7_USER_TRIAL" 'Microsoft Dynamics CRM Online' = "CRMSTANDARD" 'Microsoft Dynamics CRM Online - Portal Add-On' = "CRM_ONLINE_PORTAL" 'Microsoft Dynamics CRM Online Additional Test Instance' = "CRMTESTINSTANCE" 'Microsoft Dynamics CRM Online Basic' = "CRMPLAN2" 'Microsoft Dynamics CRM Online Instance' = "CRMINSTANCE" 'MICROSOFT DYNAMICS CRM ONLINE PROFESSIONA' = "CRMSTANDARD" 'Microsoft Dynamics CRM Online Storage Add-On' = "CRMSTORAGE" 'MICROSOFT DYNAMICS MARKETING SALES COLLABORATION - ELIGIBILITY CRITERIA APPLY' = "MDM_SALES_COLLABORATION" 'Microsoft eCDN' = "MICROSOFT_ECDN" 'Microsoft Endpoint DLP' = "MICROSOFTENDPOINTDLP" 'Microsoft Excel Advanced Analytics' = "EXCEL_PREMIUM" 'Microsoft Fabric (Free)' = "POWER_BI_STANDARD" 'Microsoft Fabric (Free) for faculty' = "POWER_BI_STANDARD_FACULTY" 'Microsoft Fabric (Free) for student' = "POWER_BI_STANDARD_STUDENT" 'Microsoft Forms (Plan 2)' = "OFFICE_FORMS_PLAN_2" 'Microsoft Forms (Plan 3)' = "OFFICE_FORMS_PLAN_3" 'MICROSOFT FORMS (PLAN E1)' = "FORMS_PLAN_E1" 'Microsoft Forms (Plan E3)' = "FORMS_PLAN_E3" 'Microsoft Forms (Plan E5)' = "FORMS_PLAN_E5" 'Microsoft Forms (Plan F1)' = "FORMS_PLAN_K" 'Microsoft Forms for Government (Plan E5)' = "FORMS_GOV_E5" 'Microsoft Imagine Academy' = "IT_ACADEMY_AD" 'Microsoft Information Governance' = "INFO_GOVERNANCE" 'Microsoft Insider Risk Management' = "INSIDER_RISK" 'Microsoft Intune' = "INTUNE_A" 'Microsoft Intune Device' = "INTUNE_A_D" 'Microsoft Intune Device for Government' = "INTUNE_A_D_GOV" 'Microsoft Intune for Education' = "INTUNE_EDU" 'Microsoft Intune Plan 1' = "INTUNE_A" 'Microsoft Intune Plan 1 for Education' = "INTUNE_EDU" 'Microsoft Intune SMB' = "INTUNE_SMB" 'Microsoft Intune Suite' = "Microsoft_Intune_Suite" 'Microsoft Invoicing' = "DYN365BC_MS_INVOICING" 'Microsoft Kaizala' = "KAIZALA_STANDALONE" 'Microsoft Kaizala Pro' = "KAIZALA_O365_P3" 'Microsoft Kaizala Pro Plan 1' = "KAIZALA_O365_P1" 'Microsoft Kaizala Pro Plan 2' = "KAIZALA_O365_P2" 'Microsoft Kaizala Pro Plan 3' = "KAIZALA_O365_P3" 'Microsoft ML-Based Classification' = "ML_CLASSIFICATION" 'Microsoft MyAnalytics (Full)' = "EXCHANGE_ANALYTICS" 'Microsoft MyAnalytics for Government (Full)' = "EXCHANGE_ANALYTICS_GOV" 'MICROSOFT PLANNE' = "PROJECTWORKMANAGEMENT" 'Microsoft Planner' = "PROJECTWORKMANAGEMENT" 'Microsoft Power Apps for Developer' = "POWERAPPS_DEV" 'Microsoft Power Apps Plan 2 (Qualified Offer)' = "POWERFLOW_P2" 'Microsoft Power Apps Plan 2 Trial' = "POWERAPPS_VIRAL" 'Microsoft Power Automate Free' = "FLOW_FREE" 'Microsoft Power Automate Plan 2' = "FLOW_P2" 'MICROSOFT POWER BI INFORMATION SERVICES PLAN' = "SQL_IS_SSIM" 'Microsoft Power BI Information Services Plan 1' = "SQL_IS_SSIM" 'Microsoft Power BI Reporting and Analytics Plan 1' = "BI_AZURE_P1" 'MICROSOFT POWER VIDEOS BASIC' = "POWERVIDEOSFREE" 'MICROSOFT POWERAPPS' = "POWERAPPSFREE" 'Microsoft Records Management' = "RECORDS_MANAGEMENT" 'Microsoft Relationship Sales solution' = "DYN365_ ENTERPRISE _RELATIONSHIP_SALES" 'Microsoft Remote Assist' = "MICROSOFT_REMOTE_ASSIST" 'Microsoft Search' = "MICROSOFT_SEARCH" 'MICROSOFT SOCIAL ENGAGEMENT - SERVICE DISCONTINUATION' = "DYN365_ENTERPRISE_CUSTOMER_SERVICE" 'Microsoft Social Engagement Enterprise' = "NBENTERPRISE" 'MICROSOFT SOCIAL ENGAGEMENT PROFESSIONAL - ELIGIBILITY CRITERIA APPLY' = "NBPROFESSIONALFORCRM" 'Microsoft StaffHub' = "Deskless" 'Microsoft Stream' = "STREAM" 'MICROSOFT STREAM FOR O365 E1 SKU' = "STREAM_O365_E1" 'Microsoft Stream for O365 E3 SKU' = "STREAM_O365_E3" 'MICROSOFT STREAM FOR O365 E5 SKU' = "STREAM_O365_E5" 'Microsoft Stream for O365 for Government (E1)' = "STREAM_O365_E1_GOV" 'MICROSOFT STREAM FOR O365 FOR GOVERNMENT (E3)' = "STREAM_O365_E3_GOV" 'Microsoft Stream for O365 for Government (F1)' = "STREAM_O365_K_GOV" 'Microsoft Stream for O365 K SKU' = "STREAM_O365_K" 'Microsoft Stream for Office 365 E1' = "STREAM_O365_E1" 'Microsoft Stream for Office 365 E3' = "STREAM_O365_E3" 'Microsoft Stream for Office 365 E5' = "STREAM_O365_E5" 'Microsoft Stream for Office 365 F3' = "STREAM_O365_K" 'Microsoft Stream Plan 2' = "STREAM_P2" 'Microsoft Stream Storage Add-On' = "STREAM_STORAGE" 'Microsoft Stream Storage Add-On (500 GB)' = "STREAM_STORAGE" 'Microsoft Teams' = "TEAMS1" 'Microsoft Teams (Free)' = "TEAMS_FREE" 'Microsoft Teams Audio Conferencing with dial-out to select geographies' = "MCOMEETBASIC" 'Microsoft Teams Audio Conferencing with dial-out to USA/CAN' = "Microsoft_Teams_Audio_Conferencing_select_dial_out" 'Microsoft Teams Commercial Cloud' = "TEAMS_COMMERCIAL_TRIAL" 'Microsoft Teams Essentials' = "Teams_Ess" 'Microsoft Teams Essentials (AAD Identity)' = "TEAMS_ESSENTIALS_AAD" 'Microsoft Teams Exploratory' = "TEAMS_EXPLORATORY" 'Microsoft Teams for DOD (AR)' = "TEAMS_AR_DOD" 'Microsoft Teams for GCCHigh (AR)' = "TEAMS_AR_GCCHIGH" 'Microsoft Teams for Government' = "TEAMS_GOV" 'Microsoft Teams Phone Resource Account' = "PHONESYSTEM_VIRTUALUSER" 'Microsoft Teams Phone Resource Account for GCC' = "PHONESYSTEM_VIRTUALUSER_GOV" 'Microsoft Teams Phone Standard' = "MCOEV" 'Microsoft Teams Phone Standard for DOD' = "MCOEV_DOD" 'Microsoft Teams Phone Standard for Faculty' = "MCOEV_FACULTY" 'Microsoft Teams Phone Standard for GCC' = "MCOEV_GOV" 'Microsoft Teams Phone Standard for GCCHIGH' = "MCOEV_GCCHIGH" 'Microsoft Teams Phone Standard for Small and Medium Business' = "MCOEVSMB_1" 'Microsoft Teams Phone Standard for Students' = "MCOEV_STUDENT" 'Microsoft Teams Phone Standard for TELSTRA' = "MCOEV_TELSTRA" 'Microsoft Teams Phone Standard_USGOV_DOD' = "MCOEV_USGOV_DOD" 'Microsoft Teams Phone Standard_USGOV_GCCHIGH' = "MCOEV_USGOV_GCCHIGH" 'Microsoft Teams Premium Intelligent' = "TEAMSPRO_MGMT" 'Microsoft Teams Premium Introductory Pricing' = "Microsoft_Teams_Premium" 'Microsoft Teams Premium Personalized' = "TEAMSPRO_CUST" 'Microsoft Teams Premium Secure' = "TEAMSPRO_PROTECTION" 'Microsoft Teams Premium Virtual Appointment' = "TEAMSPRO_VIRTUALAPPT" 'Microsoft Teams Premium Virtual Appointments' = "MCO_VIRTUAL_APPT" 'Microsoft Teams Premium Webinar' = "TEAMSPRO_WEBINAR" 'Microsoft Teams Rooms Basic' = "Microsoft_Teams_Rooms_Basic" 'Microsoft Teams Rooms Basic for EDU' = "Microsoft_Teams_Rooms_Basic_FAC" 'Microsoft Teams Rooms Basic without Audio Conferencing' = "Microsoft_Teams_Rooms_Basic_without_Audio_Conferencing" 'Microsoft Teams Rooms Pro' = "Microsoft_Teams_Rooms_Pro" 'Microsoft Teams Rooms Pro for EDU' = "Microsoft_Teams_Rooms_Pro_FAC" 'Microsoft Teams Rooms Pro Management' = "MTRProManagement" 'Microsoft Teams Rooms Pro without Audio Conferencing' = "Microsoft_Teams_Rooms_Pro_without_Audio_Conferencing" 'Microsoft Teams Rooms Standard' = "MEETING_ROOM" 'Microsoft Teams Rooms Standard without Audio Conferencing' = "MEETING_ROOM_NOAUDIOCONF" 'Microsoft Teams Shared Devices' = "MCOCAP" 'Microsoft Teams Shared Devices for GCC' = "MCOCAP_GOV" 'Microsoft Teams Trial' = "MS_TEAMS_IW" 'Microsoft Threat Experts - Experts on Demand' = "EXPERTS_ON_DEMAND" 'Microsoft Tunnel for Mobile Application Management' = "Intune-MAMTunnel" 'Microsoft Viva Goals' = "Microsoft_Viva_Goals" 'Microsoft Viva Insights' = "WORKPLACE_ANALYTICS_INSIGHTS_USER" 'Microsoft Viva Insights Backend' = "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND" 'Microsoft Viva Sales Premium & Trial' = "Microsoft_Viva_Sales_PremiumTrial" 'Microsoft Viva Sales Premium with Power Automate' = "Microsoft_Viva_Sales_PowerAutomate" 'Microsoft Viva Suite' = "VIVA" 'Microsoft Viva Topics' = "CORTEX" 'Microsoft Workplace Analytics' = "WORKPLACE_ANALYTICS" 'Microsoft Workplace Analytics Insights Backend' = "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND" 'Microsoft Workplace Analytics Insights User' = "WORKPLACE_ANALYTICS_INSIGHTS_USER" 'MICROSOFT_COMMUNICATION_COMPLIANCE' = "MICROSOFT_COMMUNICATION_COMPLIANCE" 'MICROSOFT_SEARCH' = "MICROSOFT_SEARCH" 'MICROSOFTBOOKINGS' = "MICROSOFTBOOKINGS" 'Minecraft Education' = "MINECRAFT_EDUCATION_EDITION" 'Minecraft Education Edition' = "MINECRAFT_EDUCATION_EDITION" 'Minecraft Education Faculty' = "MEE_FACULTY" 'Minecraft Education Student' = "MEE_STUDENT" 'MIP_S_CLP1' = "MIP_S_CLP1" 'MIP_S_CLP2' = "MIP_S_CLP2" 'Mobile Device Management for Office 365' = "INTUNE_O365" 'MS IMAGINE ACADEMY' = "IT_ACADEMY_AD" 'MTP' = "MTP" 'Multi-Geo Capabilities in Office 365' = "OFFICE365_MULTIGEO" 'Nonprofit Portal' = "NONPROFIT_PORTAL" 'Nucleus' = "Nucleus" 'Office 365 A1 for faculty' = "STANDARDWOFFPACK_FACULTY" 'Office 365 A1 for students' = "STANDARDWOFFPACK_STUDENT" 'Office 365 A1 Plus for faculty' = "STANDARDWOFFPACK_IW_FACULTY" 'Office 365 A1 Plus for students' = "STANDARDWOFFPACK_IW_STUDENT" 'Office 365 A3 for faculty' = "ENTERPRISEPACKPLUS_FACULTY" 'Office 365 A3 for students' = "ENTERPRISEPACKPLUS_STUDENT" 'Office 365 A5 for faculty' = "ENTERPRISEPREMIUM_FACULTY" 'Office 365 A5 for students' = "ENTERPRISEPREMIUM_STUDENT" 'Office 365 Advanced Compliance' = "EQUIVIO_ANALYTICS" 'Office 365 Advanced Compliance for GCC' = "EQUIVIO_ANALYTICS_GOV" 'Office 365 Advanced eDiscovery' = "EQUIVIO_ANALYTICS" 'Office 365 Advanced eDiscovery for Government' = "EQUIVIO_ANALYTICS_GOV" 'Office 365 Advanced Security Management' = "ADALLOM_S_O365" 'OFFICE 365 BUSINESS' = "OFFICE_BUSINESS" 'Office 365 Cloud App Security' = "ADALLOM_O365" 'Office 365 E1' = "STANDARDPACK" 'Office 365 E2' = "STANDARDWOFFPACK" 'Office 365 E3' = "ENTERPRISEPACK" 'Office 365 E3 Developer' = "DEVELOPERPACK" 'Office 365 E3_USGOV_DOD' = "ENTERPRISEPACK_USGOV_DOD" 'Office 365 E3_USGOV_GCCHIGH' = "ENTERPRISEPACK_USGOV_GCCHIGH" 'Office 365 E4' = "ENTERPRISEWITHSCAL" 'Office 365 E5' = "ENTERPRISEPREMIUM" 'Office 365 E5 Without Audio Conferencing' = "ENTERPRISEPREMIUM_NOPSTNCONF" 'Office 365 Extra File Storage' = "SHAREPOINTSTORAGE" 'Office 365 Extra File Storage for GCC' = "SHAREPOINTSTORAGE_GOV" 'Office 365 F3' = "DESKLESSPACK" 'Office 365 G1 GCC' = "STANDARDPACK_GOV" 'Office 365 G3 GCC' = "ENTERPRISEPACK_GOV" 'Office 365 G5 GCC' = "ENTERPRISEPREMIUM_GOV" 'Office 365 Midsize Business' = "MIDSIZEPACK" 'Office 365 Planner for Government' = "PROJECTWORKMANAGEMENT_GOV" 'Office 365 Privileged Access Management' = "PAM_ENTERPRISE" 'Office 365 ProPlus' = "OFFICESUBSCRIPTION" 'Office 365 SafeDocs' = "SAFEDOCS" 'Office 365 Small Business' = "LITEPACK" 'Office 365 Small Business Premium' = "LITEPACK_P2" 'OFFICE 365 SMALL BUSINESS SUBSCRIPTION' = "OFFICE_PRO_PLUS_SUBSCRIPTION_SMBIZ" 'Office for the web' = "SHAREPOINTWAC" 'Office for the web (Education)' = "SHAREPOINTWAC_EDU" 'OFFICE FOR THE WEB (GOVERNMENT)' = "SHAREPOINTWAC_GOV" 'Office for the Web for Education' = "SHAREPOINTWAC_EDU" 'Office for the Web for Government' = "SHAREPOINTWAC_GOV" 'Office Mobile Apps for Office 365' = "OFFICEMOBILE_SUBSCRIPTION" 'Office Mobile Apps for Office 365 for GCC' = "OFFICEMOBILE_SUBSCRIPTION_GOV" 'OFFICE ONLINE' = "SHAREPOINTWAC" 'OFFICE ONLINE FOR DEVELOPER' = "SHAREPOINTWAC_DEVELOPER" 'Office Shared Computer Activation' = "OFFICE_SHARED_COMPUTER_ACTIVATION" 'OFFICEMOBILE_SUBSCRIPTION' = "OFFICEMOBILE_SUBSCRIPTION" 'OFFICESUBSCRIPTION' = "OFFICESUBSCRIPTION" 'OFFICESUBSCRIPTION_GOV' = "OFFICESUBSCRIPTION_GOV" 'OneDrive for Business (Basic 2)' = "ONEDRIVE_BASIC_P2" 'OneDrive for Business (Basic)' = "ONEDRIVE_BASIC" 'OneDrive for Business (Plan 1)' = "WACONEDRIVESTANDARD" 'OneDrive for Business (Plan 2)' = "WACONEDRIVEENTERPRISE" 'OneDrive for business Basic' = "ONEDRIVE_BASIC" 'ONEDRIVE FOR BUSINESS BASIC FOR GOVERNMENT' = "ONEDRIVE_BASIC_GOV" 'ONEDRIVEENTERPRISE' = "ONEDRIVEENTERPRISE" 'ONEDRIVESTANDARD' = "ONEDRIVESTANDARD" 'OUTLOOK CUSTOMER MANAGER' = "O365_SB_Relationship_Management" 'PAD for Windows' = "POWERAUTOMATE_DESKTOP_FOR_WIN" 'Power Apps (Plan 2)' = "POWERAPPS_P2" 'Power Apps and Logic Flows' = "POWERAPPS_INDIVIDUAL_USER" 'Power Apps for Customer Service Pro' = "POWERAPPS_CUSTOMER_SERVICE_PRO" 'Power Apps for Dynamics 365' = "POWERAPPS_DYN_TEAM" 'Power Apps for Dynamics 365 vTrial' = "POWER_APPS_DYN365_VIRAL_TRIAL" 'Power Apps for Guides' = "POWERAPPS_GUIDES" 'Power Apps for Office 365' = "POWERAPPS_O365_P2" 'Power Apps for Office 365 (Plan 3)' = "POWERAPPS_O365_P3" 'Power Apps for Office 365 F3' = "POWERAPPS_O365_S1" 'Power Apps for Office 365 F3 for Government' = "POWERAPPS_O365_S1_GOV" 'Power Apps for Office 365 for Government' = "POWERAPPS_O365_P3_GOV" 'Power Apps for Sales Pro' = "POWERAPPS_SALES_PRO" 'Power Apps per app' = "POWERAPPS_PER_APP_NEW" 'Power Apps per app plan' = "POWERAPPS_PER_APP" 'Power Apps per app plan (1 app or portal)' = "POWERAPPS_PER_APP_NEW" 'Power Apps per user plan' = "POWERAPPS_PER_USER" 'Power Apps per user plan for Government' = "POWERAPPS_PER_USER_GCC" 'Power Apps Portals Login Capacity Add-On' = "POWERAPPS_PORTALS_LOGIN" 'Power Apps Portals Login Capacity Add-On for Government' = "POWERAPPS_PORTALS_LOGIN_GCC" 'Power Apps Portals login capacity add-on Tier 2 (10 unit min)' = "POWERAPPS_PORTALS_LOGIN_T2" 'Power Apps Portals login capacity add-on Tier 2 (10 unit min) for Government' = "POWERAPPS_PORTALS_LOGIN_T2_GCC" 'Power Apps Portals login capacity add-on Tier 3 (50 unit min)' = "POWERAPPS_PORTALS_LOGIN_T3" 'Power Apps Portals page view capacity add-on' = "POWERAPPS_PORTALS_PAGEVIEW" 'Power Apps Portals page view capacity add-on for Government' = "POWERAPPS_PORTALS_PAGEVIEW_GCC" 'Power Automate (Plan 1) for Government' = "FLOW_P1_GOV" 'Power Automate (Plan 2)' = "FLOW_P2" 'Power Automate for Customer Service Pro' = "FLOW_CUSTOMER_SERVICE_PRO" 'Power Automate for Dynamics 365' = "FLOW_DYN_TEAM" 'Power Automate for Dynamics 365 Customer Voice' = "FLOW_FORMS_PRO" 'Power Automate for Dynamics 365 vTrial' = "POWER_AUTOMATE_DYN365_VIRAL_TRIAL" 'Power Automate for Office 365' = "FLOW_O365_P2" 'Power Automate for Office 365 F3' = "FLOW_O365_S1" 'Power Automate for Office 365 F3 for Government' = "FLOW_O365_S1_GOV" 'Power Automate for Office 365 for Government' = "FLOW_O365_P3_GOV" 'Power Automate for Power Apps per App Plan' = "Flow_Per_APP" 'Power Automate for Power Apps per User Plan' = "Flow_PowerApps_PerUser" 'Power Automate for Power Apps per User Plan for GCC' = "Flow_PowerApps_PerUser_GCC" 'Power Automate for Project' = "FLOW_FOR_PROJECT" 'POWER AUTOMATE FOR PROJECT P1' = "Power_Automate_For_Project_P1" 'Power Automate for Virtual Agent' = "FLOW_VIRTUAL_AGENT_BASE" 'Power Automate per flow plan' = "FLOW_BUSINESS_PROCESS" 'Power Automate per user plan' = "FLOW_PER_USER" 'Power Automate per user plan dept' = "FLOW_PER_USER_DEPT" 'Power Automate per user plan for Government' = "FLOW_PER_USER_GCC" 'Power Automate per user with attended RPA plan' = "POWERAUTOMATE_ATTENDED_RPA" 'Power Automate RPA Attended' = "POWER_AUTOMATE_ATTENDED_RPA" 'Power Automate unattended RPA add-on' = "POWERAUTOMATE_UNATTENDED_RPA" 'Power BI' = "POWER_BI_INDIVIDUAL_USER" 'Power BI (free)' = "POWER_BI_STANDARD" 'Power BI for Office 365 Add-On' = "POWER_BI_ADDON" 'Power BI Premium P' = "PBI_PREMIUM_P1_ADDON" 'Power BI Premium P1' = "PBI_PREMIUM_P1_ADDON" 'Power BI Premium Per User' = "PBI_PREMIUM_PER_USER" 'Power BI Premium Per User Add-On' = "PBI_PREMIUM_PER_USER_ADDON" 'Power BI Premium Per User Dept' = "PBI_PREMIUM_PER_USER_DEPT" 'Power BI Premium Per User for Faculty' = "PBI_PREMIUM_PER_USER_FACULTY" 'Power BI Pro' = "POWER_BI_PRO" 'Power BI Pro CE' = "POWER_BI_PRO_CE" 'Power BI Pro Dept' = "POWER_BI_PRO_DEPT" 'Power BI Pro for Faculty' = "POWER_BI_PRO_FACULTY" 'Power BI Pro for GCC' = "POWERBI_PRO_GOV" 'Power BI Pro for Government' = "BI_AZURE_P_2_GOV" 'Power Pages Internal User' = "Power_Pages_Internal_User" 'Power Pages vTrial for Makers' = "Power_Pages_vTrial_for_Makers" 'Power Virtual Agent' = "VIRTUAL_AGENT_BASE" 'Power Virtual Agent User License' = "VIRTUAL_AGENT_USL" 'Power Virtual Agents for Chat' = "POWER_VIRTUAL_AGENTS_D365_CS_CHAT" 'Power Virtual Agents for Customer Service Voice' = "POWER_VIRTUAL_AGENTS_D365_CS_VOICE" 'Power Virtual Agents for Digital Messaging' = "POWER_VIRTUAL_AGENTS_D365_CS_MESSAGING" 'Power Virtual Agents for Office 365' = "POWER_VIRTUAL_AGENTS_O365_P2" 'Power Virtual Agents for Office 365 F1' = "POWER_VIRTUAL_AGENTS_O365_F1" 'POWER VIRTUAL AGENTS FOR OFFICE 365 P1' = "POWER_VIRTUAL_AGENTS_O365_P1" 'Power Virtual Agents for Office 365 P2' = "POWER_VIRTUAL_AGENTS_O365_P2" 'Power Virtual Agents for Office 365 P3' = "POWER_VIRTUAL_AGENTS_O365_P3" 'Power Virtual Agents Viral Trial' = "CCIBOTS_PRIVPREV_VIRAL" 'PowerApps for Developer' = "POWERAPPS_DEV_VIRAL" 'PowerApps for Dynamics 365' = "POWERAPPS_DYN_APPS" 'POWERAPPS FOR OFFICE 36' = "POWERAPPS_O365_P2" 'POWERAPPS FOR OFFICE 365' = "POWERAPPS_O365_P1" 'PowerApps for Office 365 Plan 3' = "POWERAPPS_O365_P3" 'PowerApps per app baseline access' = "POWERAPPS_PER_APP_IW" 'PowerApps Plan 1 for Government' = "POWERAPPS_P1_GOV" 'PowerApps Trial' = "POWERAPPS_P2_VIRAL" 'POWERAPPS_O365_P3_GOV' = "POWERAPPS_O365_P3_GOV" 'Premium Encryption in Office 365' = "PREMIUM_ENCRYPTION" 'PREMIUM_ENCRYPTION' = "PREMIUM_ENCRYPTION" 'Priva - Risk' = "PRIVACY_MANGEMENT_RISK" 'Priva - Risk (Exchange)' = "PRIVACY_MANGEMENT_RISK_EXCHANGE" 'Privacy Management � risk' = "PRIVACY_MANAGEMENT_RISK" 'Privacy Management - risk for EDU' = "PRIVACY_MANAGEMENT_RISK_EDU" 'Privacy Management - risk GCC' = "PRIVACY_MANAGEMENT_RISK_GCC" 'Privacy Management - risk_USGOV_DOD' = "PRIVACY_MANAGEMENT_RISK_USGOV_DOD" 'Privacy Management - risk_USGOV_GCCHIGH' = "PRIVACY_MANAGEMENT_RISK_USGOV_GCCHIGH" 'Privacy Management - Subject Rights Request' = "PRIVACY_MANGEMENT_DSR" 'Privacy Management - Subject Rights Request (1 - Exchange)' = "PRIVACY_MANGEMENT_DSR_1" 'Privacy Management - subject rights request (1)' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2" 'Privacy Management - subject rights request (1) for EDU' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_EDU_V2" 'Privacy Management - subject rights request (1) GCC' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_GCC" 'Privacy Management - subject rights request (1) USGOV_DOD' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_DOD" 'Privacy Management - subject rights request (1) USGOV_GCCHIGH' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_1_V2_USGOV_GCCHIGH" 'Privacy Management - Subject Rights Request (10 - Exchange)' = "PRIVACY_MANGEMENT_DSR_EXCHANGE_10" 'Privacy Management - subject rights request (10)' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2" 'Privacy Management - subject rights request (10) for EDU' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_EDU_V2" 'Privacy Management - subject rights request (10) GCC' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_GCC" 'Privacy Management - subject rights request (10) USGOV_DOD' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_DOD" 'Privacy Management - subject rights request (10) USGOV_GCCHIGH' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_10_V2_USGOV_GCCHIGH" 'Privacy Management - Subject Rights Request (100 - Exchange)' = "PRIVACY_MANGEMENT_DSR_EXCHANGE_100" 'Privacy Management - subject rights request (100)' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2" 'Privacy Management - subject rights request (100) for EDU' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_EDU_V2" 'Privacy Management - subject rights request (100) GCC' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_GCC" 'Privacy Management - subject rights request (100) USGOV_DOD' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_DOD" 'Privacy Management - subject rights request (100) USGOV_GCCHIGH' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_100_V2_USGOV_GCCHIGH" 'Privacy Management - subject rights request (50)' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_V2" 'Privacy Management - subject rights request (50) for EDU' = "PRIVACY_MANAGEMENT_SUB_RIGHTS_REQ_50_EDU_V2" 'Privacy Management - Subject Rights Request (Exchange)' = "PRIVACY_MANGEMENT_DSR_EXCHANGE" 'Project for Office (Plan E1)' = "PROJECT_O365_P1" 'Project for Office (Plan E3)' = "PROJECT_O365_P2" 'Project for Office (Plan E5)' = "PROJECT_O365_P3" 'Project for Office (Plan F)' = "PROJECT_O365_F3" 'Project for Office 365' = "PROJECTCLIENT" 'Project for Project Operations' = "PROJECT_FOR_PROJECT_OPERATIONS" 'Project Online Desktop Client' = "PROJECT_CLIENT_SUBSCRIPTION" 'Project Online Desktop Client for Government' = "PROJECT_CLIENT_SUBSCRIPTION_GOV" 'Project Online Essentials' = "PROJECTESSENTIALS" 'Project Online Essentials for Faculty' = "PROJECTESSENTIALS_FACULTY" 'Project Online Essentials for GCC' = "PROJECTESSENTIALS_GOV" 'Project Online Essentials for Government' = "PROJECT_ESSENTIALS_GOV" 'Project Online Premium' = "PROJECTPREMIUM" 'Project Online Premium Without Project Client' = "PROJECTONLINE_PLAN_1" 'Project Online Service' = "SHAREPOINT_PROJECT" 'Project Online Service for Education' = "SHAREPOINT_PROJECT_EDU" 'Project Online Service for Government' = "SHAREPOINT_PROJECT_GOV" 'Project Online With Project for Office 365' = "PROJECTONLINE_PLAN_2" 'PROJECT P1' = "PROJECT_P1" 'Project P3' = "PROJECT_PROFESSIONAL" 'Project P3 for Faculty' = "PROJECT_PROFESSIONAL_FACULTY" 'Project Plan 1' = "PROJECT_P1" 'Project Plan 1 (for Department)' = "PROJECT_PLAN1_DEPT" 'Project Plan 3' = "PROJECTPROFESSIONAL" 'Project Plan 3 (for Department)' = "PROJECT_PLAN3_DEPT" 'Project Plan 3 for Faculty' = "PROJECTPROFESSIONAL_FACULTY" 'Project Plan 3 for GCC' = "PROJECTPROFESSIONAL_GOV" 'Project Plan 5 for GCC' = "PROJECTPREMIUM_GOV" 'Project Plan 5 without Project Client for Faculty' = "PROJECTONLINE_PLAN_1_FACULTY" 'PROJECTWORKMANAGEMENT' = "PROJECTWORKMANAGEMENT" 'PROJECTWORKMANAGEMENT_GOV' = "PROJECTWORKMANAGEMENT_GOV" 'RECORDS_MANAGEMENT' = "RECORDS_MANAGEMENT" 'Remote help' = "REMOTE_HELP" 'RETIRED - Field Service � Automated Routing Engine Add-On' = "CRM_AUTO_ROUTING_ADDON" 'RETIRED - Microsoft Communications Compliance' = "COMMUNICATIONS_COMPLIANCE" 'RETIRED - Microsoft Insider Risk Management' = "INSIDER_RISK_MANAGEMENT" 'Retired - Microsoft Social Engagement' = "NBENTERPRISE" 'RETIRED - Outlook Customer Manager' = "O365_SB_Relationship_Management" 'Rights Management Adhoc' = "RIGHTSMANAGEMENT_ADHOC" 'Rights Management Service Basic Content Protection' = "RMSBASIC" 'RMS_S_ENTERPRISE' = "RMS_S_ENTERPRISE" 'RMS_S_ENTERPRISE_GOV' = "RMS_S_ENTERPRISE_GOV" 'RMS_S_PREMIUM' = "RMS_S_PREMIUM" 'School Data Sync (Plan 1)' = "SCHOOL_DATA_SYNC_P1" 'School Data Sync (Plan 2)' = "SCHOOL_DATA_SYNC_P2" 'SecOps Investigation for MDI' = "ADALLOM_FOR_AATP" 'Sensor Data Intelligence Additional Machines Add-in for Dynamics 365 Supply Chain Management' = "DYN365_IOT_INTELLIGENCE_ADDL_MACHINES" 'Sensor Data Intelligence Scenario Add-in for Dynamics 365 Supply Chain Management' = "DYN365_IOT_INTELLIGENCE_SCENARIO" 'SHAREPOINT' = "SHAREPOINTSTANDARD" 'SharePoint (Plan 1)' = "SHAREPOINTSTANDARD" 'SharePoint (Plan 1) for Education' = "SHAREPOINTSTANDARD_EDU" 'SharePoint (Plan 2)' = "SHAREPOINTENTERPRISE" 'SharePoint (Plan 2) for Education' = "SHAREPOINTENTERPRISE_EDU" 'SharePoint (Plan 2)Dynamics 365 for Sales Pro Attach' = "SHAREPOINTENTERPRISE" 'SHAREPOINT FOR DEVELOPER' = "SHAREPOINT_S_DEVELOPER" 'SharePoint Kiosk' = "SHAREPOINTDESKLESS" 'SharePoint KioskG' = "SHAREPOINTDESKLESS_GOV" 'SharePoint Multi-Geo' = "SHAREPOINTONLINE_MULTIGEO" 'SharePoint Online (Plan 1)' = "SHAREPOINTSTANDARD" 'SharePoint Online (Plan 2)' = "SHAREPOINTENTERPRISE" 'SharePoint Online Kiosk' = "SHAREPOINTDESKLESS" 'SHAREPOINT PLAN 1' = "SHAREPOINTENTERPRISE_MIDMARKET" 'SharePoint Plan 1G' = "SharePoint Plan 1G" 'SharePoint Plan 2 for EDU' = "SHAREPOINTENTERPRISE_EDU" 'SharePoint Plan 2G' = "SHAREPOINTENTERPRISE_GOV" 'SHAREPOINT STANDARD' = "SHAREPOINTSTANDARD" 'SharePoint Syntex' = "Intelligent_Content_Services" 'SharePoint Syntex - SPO type' = "Intelligent_Content_Services_SPO_type" 'SHAREPOINT_PROJECT' = "SHAREPOINT_PROJECT" 'SHAREPOINTDESKLESS' = "SHAREPOINTDESKLESS" 'SHAREPOINTENTERPRISE_GOV' = "SHAREPOINTENTERPRISE_GOV" 'SHAREPOINTLITE' = "SHAREPOINTLITE" 'SHAREPOINTSTANDARD' = "SHAREPOINTSTANDARD" 'SHAREPOINTSTORAGE_GOV' = "SHAREPOINTSTORAGE_GOV" 'SHAREPOINTWAC_GOV' = "SHAREPOINTWAC_GOV" 'SKYPE FOR BUSINESS CLOUD PBX FOR SMALL AND MEDIUM BUSINESS' = "MCOEVSMB" 'Skype for Business Online (Plan 1)' = "MCOIMP" 'Skype for Business Online (Plan 1) for Government' = "MCOIMP_GOV" 'Skype for Business Online (Plan 2)' = "MCOSTANDARD" 'Skype for Business Online (Plan 2) for Government' = "MCOSTANDARD_GOV" 'SKYPE FOR BUSINESS ONLINE (PLAN 2) FOR MIDSIZ' = "MCOSTANDARD_MIDMARKET" 'SKYPE FOR BUSINESS ONLINE (PLAN 3)' = "MCOVOICECONF" 'SKYPE FOR BUSINESS ONLINE (PLAN P1)' = "MCOLITE" 'Skype for Business PSTN Domestic and International Calling' = "MCOPSTN2" 'Skype for Business PSTN Domestic Calling' = "MCOPSTN1" 'Skype for Business PSTN Domestic Calling (120 Minutes)' = "MCOPSTN5" 'Skype for Business PSTN Usage Calling Plan' = "MCOPSTNPP" 'Stream for Office 365' = "STREAM_O365_SMB" 'Stream for Office 365 for Government (E5)' = "STREAM_O365_E5_GOV" 'STREAM_O365_E5_GOV' = "STREAM_O365_E5_GOV" 'STREAM_O365_K' = "STREAM_O365_K" 'Sway' = "SWAY" 'TEAMS FREE SERVICE' = "TEAMS_FREE_SERVICE" 'Teams Multi-Geo' = "TEAMSMULTIGEO" 'Teams Phone with Calling Plan' = "MCOTEAMS_ESSENTIALS" 'Teams Room Basic' = "Teams_Room_Basic" 'Teams Room Pro' = "Teams_Room_Pro" 'Teams Room Standard' = "Teams_Room_Standard" 'Teams Rooms Premium' = "MTR_PREM" 'Teams Rooms Test 1' = "Teams_Room_Basic" 'Teams Rooms Test 2' = "Teams_Room_Pro" 'TEAMS_GOV' = "TEAMS_GOV" 'TEAMS1' = "TEAMS1" 'TELSTRA Calling for O365' = "MCOPSTNEAU2" 'THREAT_INTELLIGENCE_GOV' = "THREAT_INTELLIGENCE_GOV" 'To-Do (Firstline)' = "BPOS_S_TODO_FIRSTLINE" 'To-Do (Plan 1)' = "BPOS_S_TODO_1" 'To-Do (Plan 2)' = "BPOS_S_TODO_2" 'To-Do (Plan 3)' = "BPOS_S_TODO_3" 'Universal Print' = "UNIVERSAL_PRINT" 'Universal Print Without Seeding' = "UNIVERSAL_PRINT_NO_SEEDING" 'Virtual Agent' = "VIRTUAL_AGENT_USL" 'Virtual Agent Base' = "VIRTUAL_AGENT_BASE" 'Visio Desktop App' = "VISIO_CLIENT_SUBSCRIPTION" 'VISIO DESKTOP APP FOR Government' = "VISIO_CLIENT_SUBSCRIPTION_GOV" 'Visio Online Plan 1' = "VISIOONLINE_PLAN1" 'Visio Online Plan 2' = "VISIOCLIENT" 'Visio Plan 1' = "VISIO_PLAN1_DEPT" 'Visio Plan 2' = "VISIO_PLAN2_DEPT" 'Visio Plan 2 for Faculty' = "VISIOCLIENT_FACULTY" 'Visio Plan 2 for GCC' = "VISIOCLIENT_GOV" 'Visio web app' = "VISIOONLINE" 'VISIO WEB APP FOR GOVERNMENT' = "VISIOONLINE_GOV" 'Viva Engage Communities and Communications' = "VIVAENGAGE_COMMUNITIES_AND_COMMUNICATIONS" 'Viva Engage Core' = "VIVAENGAGE_CORE" 'Viva Engage Knowledge' = "VIVAENGAGE_KNOWLEDGE" 'Viva Goals' = "Viva_Goals_Premium" 'Viva Learning' = "VIVA_LEARNING_PREMIUM" 'Viva Learning Seeded' = "VIVA_LEARNING_SEEDED" 'Viva Topics' = "TOPIC_EXPERIENCES" 'Whiteboard (Firstline)' = "WHITEBOARD_FIRSTLINE1" 'Whiteboard (Plan 1)' = "WHITEBOARD_PLAN1" 'Whiteboard (Plan 2)' = "WHITEBOARD_PLAN2" 'Whiteboard (Plan 3)' = "WHITEBOARD_PLAN3" 'WINDOWS 10 ENTERPRISE' = "WIN10_PRO_ENT_SUB" 'Windows 10 Enterprise (New)' = "Virtualization Rights for Windows 10 (E3/E5+VDA)" 'Windows 10 Enterprise E3 (Local Only)' = "WIN10_ENT_LOC_F1" 'Windows 10/11 Business' = "WINBIZ" 'Windows 10/11 Enterprise' = "Virtualization Rights for Windows 10 (E3/E5+VDA)" 'Windows 10/11 Enterprise (Original)' = "WIN10_PRO_ENT_SUB" 'Windows 10/11 Enterprise A3 for faculty' = "WIN10_ENT_A3_FAC" 'Windows 10/11 Enterprise A3 for students' = "WIN10_ENT_A3_STU" 'Windows 10/11 Enterprise A5 for faculty' = "WIN10_ENT_A5_FAC" 'Windows 10/11 Enterprise E3' = "WIN10_VDA_E3" 'Windows 10/11 Enterprise E3 VDA' = "E3_VDA_only" 'Windows 10/11 Enterprise E5' = "WIN10_VDA_E5" 'Windows 10/11 Enterprise E5 (Original)' = "WIN_ENT_E5" 'Windows 10/11 Enterprise E5 Commercial (GCC Compatible)' = "WINE5_GCC_COMPAT" 'Windows 365 Business 1 vCPU 2 GB 64 GB' = "CPC_B_1C_2RAM_64GB" 'Windows 365 Business 2 vCPU 4 GB 128 GB' = "CPC_B_2C_4RAM_128GB" 'Windows 365 Business 2 vCPU 4 GB 256 GB' = "CPC_B_2C_4RAM_256GB" 'Windows 365 Business 2 vCPU 4 GB 64 GB' = "CPC_B_2C_4RAM_64GB" 'Windows 365 Business 2 vCPU 8 GB 128 GB' = "CPC_B_2C_8RAM_128GB" 'Windows 365 Business 2 vCPU 8 GB 256 GB' = "CPC_B_2C_8RAM_256GB" 'Windows 365 Business 2 vCPU, 8 GB, 128 GB' = "CPC_SS_2" 'Windows 365 Business 4 vCPU 16 GB 128 GB' = "CPC_B_4C_16RAM_128GB" 'Windows 365 Business 4 vCPU 16 GB 128 GB (with Windows Hybrid Benefit)' = "CPC_B_4C_16RAM_128GB_WHB" 'Windows 365 Business 4 vCPU 16 GB 256 GB' = "CPC_B_4C_16RAM_256GB" 'Windows 365 Business 4 vCPU 16 GB 512 GB' = "CPC_B_4C_16RAM_512GB" 'Windows 365 Business 8 vCPU 32 GB 128 GB' = "CPC_B_8C_32RAM_128GB" 'Windows 365 Business 8 vCPU 32 GB 256 GB' = "CPC_B_8C_32RAM_256GB" 'Windows 365 Business 8 vCPU 32 GB 512 GB' = "CPC_B_8C_32RAM_512GB" 'Windows 365 Enterprise 1 vCPU 2 GB 64 GB' = "CPC_E_1C_2GB_64GB" 'Windows 365 Enterprise 2 vCPU 4 GB 128 GB' = "CPC_E_2C_4GB_128GB" 'Windows 365 Enterprise 2 vCPU 4 GB 128 GB (Preview)' = "CPC_LVL_1" 'Windows 365 Enterprise 2 vCPU 4 GB 256 GB' = "CPC_E_2C_4GB_256GB" 'Windows 365 Enterprise 2 vCPU 4 GB 64 GB' = "CPC_E_2C_4GB_64GB" 'Windows 365 Enterprise 2 vCPU 8 GB 128 GB' = "CPC_E_2C_8GB_128GB" 'Windows 365 Enterprise 2 vCPU 8 GB 128 GB (Preview)' = "CPC_LVL_2" 'Windows 365 Enterprise 2 vCPU 8 GB 256 GB' = "CPC_E_2C_8GB_256GB" 'Windows 365 Enterprise 4 vCPU 16 GB 128 GB' = "CPC_E_4C_16GB_128GB" 'Windows 365 Enterprise 4 vCPU 16 GB 256 GB' = "CPC_E_4C_16GB_256GB" 'Windows 365 Enterprise 4 vCPU 16 GB 256 GB (Preview)' = "CPC_LVL_3" 'Windows 365 Enterprise 4 vCPU 16 GB 512 GB' = "CPC_E_4C_16GB_512GB" 'Windows 365 Enterprise 8 vCPU 32 GB 128 GB' = "CPC_E_8C_32GB_128GB" 'Windows 365 Enterprise 8 vCPU 32 GB 256 GB' = "CPC_E_8C_32GB_256GB" 'Windows 365 Enterprise 8 vCPU 32 GB 512 GB' = "CPC_E_8C_32GB_512GB" 'Windows 365 Shared Use 2 vCPU 4 GB 128 GB' = "Windows_365_S_2vCPU_4GB_128GB" 'Windows 365 Shared Use 2 vCPU 4 GB 256 GB' = "Windows_365_S_2vCPU_4GB_256GB" 'Windows 365 Shared Use 2 vCPU 4 GB 64 GB' = "Windows_365_S_2vCPU_4GB_64GB" 'Windows 365 Shared Use 2 vCPU 8 GB 128 GB' = "Windows_365_S_2vCPU_8GB_128GB" 'Windows 365 Shared Use 2 vCPU 8 GB 256 GB' = "Windows_365_S_2vCPU_8GB_256GB" 'Windows 365 Shared Use 4 vCPU 16 GB 128 GB' = "Windows_365_S_4vCPU_16GB_128GB" 'Windows 365 Shared Use 4 vCPU 16 GB 256 GB' = "Windows_365_S_4vCPU_16GB_256GB" 'Windows 365 Shared Use 4 vCPU 16 GB 512 GB' = "Windows_365_S_4vCPU_16GB_512GB" 'Windows 365 Shared Use 8 vCPU 32 GB 128 GB' = "Windows_365_S_8vCPU_32GB_128GB" 'Windows 365 Shared Use 8 vCPU 32 GB 256 GB' = "Windows_365_S_8vCPU_32GB_256GB" 'Windows 365 Shared Use 8 vCPU 32 GB 512 GB' = "Windows_365_S_8vCPU_32GB_512GB" 'Windows Autopatch' = "Windows_Autopatch" 'Windows Store for Business' = "WINDOWS_STORE" 'Windows Store for Business EDU Faculty' = "WSFB_EDU_FACULTY" 'Windows Store for Business EDU Store_faculty' = "Windows Store for Business EDU Store_faculty" 'Windows Store Service' = "WINDOWS_STORE" 'Windows Update for Business Deployment Service' = "WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE" 'YAMMER ENTERPRIS' = "YAMMER_ENTERPRISE" 'Yammer Enterprise' = "YAMMER_ENTERPRISE" 'Yammer for Academic' = "YAMMER_EDU" 'YAMMER MIDSIZE' = "YAMMER_MIDSIZE" 'YAMMER_ENTERPRISE' = "YAMMER_ENTERPRISE" 'YAMMER_MIDSIZE' = "YAMMER_MIDSIZE" } } Process { if (-not $ToSku) { $ConvertedLicenses = foreach ($LicenseToProcess in $License) { if ($LicenseToProcess -is [string]) { $L = $LicenseToProcess } elseif ($LicenseToProcess -is [Microsoft.Online.Administration.UserLicense]) { $L = $LicenseToProcess.AccountSkuId } else { continue } $L = $L -replace '.*(:)' $Conversion = $O365SKU[$L] if ($null -eq $Conversion) { $L } else { $Conversion } } } else { $ConvertedLicenses = :Outer foreach ($L in $License) { $Conversion = $SKUO365[$L] if ($null -eq $Conversion) { $L } else { $Conversion } } } if ($ReturnArray) { $ConvertedLicenses } else { $ConvertedLicenses -join $Separator } } End { } } function Convert-Size { <# .SYNOPSIS Converts a value from one size unit to another. .DESCRIPTION This function converts a value from one size unit (Bytes, KB, MB, GB, TB) to another size unit based on the specified conversion. It provides flexibility to handle different size units and precision of the conversion. .PARAMETER From Specifies the original size unit of the input value. .PARAMETER To Specifies the target size unit to convert the input value to. .PARAMETER Value Specifies the numerical value to be converted. .PARAMETER Precision Specifies the number of decimal places to round the converted value to. Default is 4. .PARAMETER Display Indicates whether to display the converted value with the target size unit. .EXAMPLE Convert-Size -From 'KB' -To 'MB' -Value 2048 # Converts 2048 Kilobytes to Megabytes. .EXAMPLE Convert-Size -From 'GB' -To 'TB' -Value 2.5 -Precision 2 -Display # Converts 2.5 Gigabytes to Terabytes with a precision of 2 decimal places and displays the result. #> # Original - https://techibee.com/powershell/convert-from-any-to-any-bytes-kb-mb-gb-tb-using-powershell/2376 # # Changelog - Modified 30.03.2018 - przemyslaw.klys at evotec.pl # - Added $Display Switch [cmdletbinding()] param( [validateset("Bytes", "KB", "MB", "GB", "TB")] [string]$From, [validateset("Bytes", "KB", "MB", "GB", "TB")] [string]$To, [Parameter(Mandatory = $true)] [double]$Value, [int]$Precision = 4, [switch]$Display ) switch ($From) { "Bytes" { $value = $Value } "KB" { $value = $Value * 1024 } "MB" { $value = $Value * 1024 * 1024 } "GB" { $value = $Value * 1024 * 1024 * 1024 } "TB" { $value = $Value * 1024 * 1024 * 1024 * 1024 } } switch ($To) { "Bytes" { return $value } "KB" { $Value = $Value / 1KB } "MB" { $Value = $Value / 1MB } "GB" { $Value = $Value / 1GB } "TB" { $Value = $Value / 1TB } } if ($Display) { return "$([Math]::Round($value,$Precision,[MidPointRounding]::AwayFromZero)) $To" } else { return [Math]::Round($value, $Precision, [MidPointRounding]::AwayFromZero) } } function Convert-TimeToDays { <# .SYNOPSIS Converts the time span between two dates into the number of days. .DESCRIPTION This function calculates the number of days between two given dates. It allows for flexibility in handling different date formats and provides an option to ignore specific dates. .PARAMETER StartTime Specifies the start date and time of the time span. .PARAMETER EndTime Specifies the end date and time of the time span. .PARAMETER Ignore Specifies a pattern to ignore specific dates. Default is '*1601*'. .EXAMPLE Convert-TimeToDays -StartTime (Get-Date).AddDays(-5) -EndTime (Get-Date) # Calculates the number of days between 5 days ago and today. .EXAMPLE Convert-TimeToDays -StartTime '2022-01-01' -EndTime '2022-01-10' -Ignore '*2022*' # Calculates the number of days between January 1, 2022, and January 10, 2022, ignoring any dates containing '2022'. #> [CmdletBinding()] param ( $StartTime, $EndTime, #[nullable[DateTime]] $StartTime, # can't use this just yet, some old code uses strings in StartTime/EndTime. #[nullable[DateTime]] $EndTime, # After that's fixed will change this. [string] $Ignore = '*1601*' ) if ($null -ne $StartTime -and $null -ne $EndTime) { try { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End $EndTime).Days } } catch { } } elseif ($null -ne $EndTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start (Get-Date) -End ($EndTime)).Days } } elseif ($null -ne $StartTime) { if ($StartTime -notlike $Ignore -and $EndTime -notlike $Ignore) { $Days = (New-TimeSpan -Start $StartTime -End (Get-Date)).Days } } return $Days } function Convert-ToDateTime { <# .SYNOPSIS Converts a file time string to a DateTime object. .DESCRIPTION This function converts a file time string to a DateTime object. It handles the conversion and provides flexibility to ignore specific file time strings. .PARAMETER Timestring Specifies the file time string to convert to a DateTime object. .PARAMETER Ignore Specifies a pattern to ignore specific file time strings. Default is '*1601*'. .EXAMPLE Convert-ToDateTime -Timestring '132479040000000000' # Converts the file time string '132479040000000000' to a DateTime object. .EXAMPLE Convert-ToDateTime -Timestring '132479040000000000' -Ignore '*1601*' # Converts the file time string '132479040000000000' to a DateTime object, ignoring any file time strings containing '1601'. #> [CmdletBinding()] param ( [string] $Timestring, [string] $Ignore = '*1601*' ) Try { $DateTime = ([datetime]::FromFileTime($Timestring)) } catch { $DateTime = $null } if ($null -eq $DateTime -or $Timestring -like $Ignore) { return $null } else { return $DateTime } } function Convert-ToTimeSpan { <# .SYNOPSIS Calculates the time span between two given DateTime values. .DESCRIPTION This function calculates the time span between two specified DateTime values. It takes a start time and an end time as input parameters and returns the TimeSpan object representing the duration between them. .PARAMETER StartTime Specifies the start DateTime value. If not provided, the current date and time will be used as the default. .PARAMETER EndTime Specifies the end DateTime value. .EXAMPLE Convert-ToTimeSpan -StartTime (Get-Date).AddDays(-5) -EndTime (Get-Date) # Calculates the time span between 5 days ago and today. .EXAMPLE Convert-ToTimeSpan -StartTime '2022-01-01' -EndTime '2022-01-10' # Calculates the time span between January 1, 2022, and January 10, 2022. #> [CmdletBinding()] param ( [DateTime] $StartTime = (Get-Date), [DateTime] $EndTime ) if ($StartTime -and $EndTime) { try { $TimeSpan = (New-TimeSpan -Start $StartTime -End $EndTime) } catch { $TimeSpan = $null } } if ($null -ne $TimeSpan) { return $TimeSpan } else { return $null } } function Convert-TwoArraysIntoOne { <# .SYNOPSIS Combines two arrays into a single array by pairing elements from each array. .DESCRIPTION This function takes two arrays as input and combines them into a single array by pairing elements from each array. It creates a new array where each element is a combination of an element from the first array and the corresponding element from the second array. .PARAMETER Object Specifies the first array containing elements to be combined. .PARAMETER ObjectToAdd Specifies the second array containing elements to be paired with elements from the first array. .EXAMPLE Convert-TwoArraysIntoOne -Object @("A", "B", "C") -ObjectToAdd @(1, 2, 3) # Combines the arrays ["A", "B", "C"] and [1, 2, 3] into a single array: ["A (1)", "B (2)", "C (3)"]. .EXAMPLE $Array1 = @("Apple", "Banana", "Cherry") $Array2 = @(5, 10, 15) Convert-TwoArraysIntoOne -Object $Array1 -ObjectToAdd $Array2 # Combines the arrays $Array1 and $Array2 into a single array where each element pairs an item from $Array1 with the corresponding item from $Array2. #> [CmdletBinding()] param ( $Object, $ObjectToAdd ) $Value = for ($i = 0; $i -lt $Object.Count; $i++) { "$($Object[$i]) ($($ObjectToAdd[$i]))" } return $Value } Function Convert-UAC { <# .SYNOPSIS Converts values from Events into proper format .DESCRIPTION Converts values from Events into proper format .PARAMETER UAC Parameter description .EXAMPLE Convert-UAC -UAC '%%1793' Convert-UAC -UAC '1793' Output: TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, RESERVED Convert-UAC -UAC '1793', '1794' Convert-UAC -UAC '121793' Output: PASSWD_CANT_CHANGE, ENCRYPTED_TEXT_PWD_ALLOWED, TEMP_DUPLICATE_ACCOUNT, NORMAL_ACCOUNT, INTERDOMAIN_TRUST_ACCOUNT, WORKSTATION_TRUST_ACCOUNT, RESERVED, RESERVED, DONT_EXPIRE_PASSWORD Convert-UAC -UAC 'C:\Onet33' Output: Same input as output Convert-UAC -UAC '121793' -OutputPerLine Output: One entry per line PASSWD_CANT_CHANGE ENCRYPTED_TEXT_PWD_ALLOWED TEMP_DUPLICATE_ACCOUNT NORMAL_ACCOUNT INTERDOMAIN_TRUST_ACCOUNT WORKSTATION_TRUST_ACCOUNT RESERVED RESERVED DONT_EXPIRE_PASSWORD .NOTES General notes #> [CmdletBinding()] param( [string[]] $UAC, [string] $Separator ) $Output = foreach ($String in $UAC) { $NumberAsString = $String.Replace('%', '') -as [int] if ($null -eq $NumberAsString) { return $UAC } $PropertyFlags = @( "SCRIPT", "ACCOUNTDISABLE", "RESERVED", "HOMEDIR_REQUIRED", "LOCKOUT", "PASSWD_NOTREQD", "PASSWD_CANT_CHANGE", "ENCRYPTED_TEXT_PWD_ALLOWED", "TEMP_DUPLICATE_ACCOUNT", "NORMAL_ACCOUNT", "RESERVED", "INTERDOMAIN_TRUST_ACCOUNT", "WORKSTATION_TRUST_ACCOUNT", "SERVER_TRUST_ACCOUNT", "RESERVED", "RESERVED", "DONT_EXPIRE_PASSWORD", "MNS_LOGON_ACCOUNT", "SMARTCARD_REQUIRED", "TRUSTED_FOR_DELEGATION", "NOT_DELEGATED", "USE_DES_KEY_ONLY", "DONT_REQ_PREAUTH", "PASSWORD_EXPIRED", "TRUSTED_TO_AUTH_FOR_DELEGATION", "RESERVED", "PARTIAL_SECRETS_ACCOUNT" "RESERVED" "RESERVED" "RESERVED" "RESERVED" "RESERVED" ) 1..($PropertyFlags.Length) | Where-Object { $NumberAsString -bAnd [math]::Pow(2, $_) } | ForEach-Object { $PropertyFlags[$_] } } if ($Separator -eq '') { $Output } else { $Output -join $Separator } } function Convert-UserAccountControl { <# .SYNOPSIS Converts the UserAccountControl flags to their corresponding names. .DESCRIPTION This function takes a UserAccountControl value and converts it into a human-readable format by matching the flags to their corresponding names. .PARAMETER UserAccountControl Specifies the UserAccountControl value to be converted. .PARAMETER Separator Specifies the separator to use when joining the converted flags. If not provided, the flags will be returned as a list. .EXAMPLE Convert-UserAccountControl -UserAccountControl 66048 Outputs: "DONT_EXPIRE_PASSWORD, PASSWORD_EXPIRED" .EXAMPLE Convert-UserAccountControl -UserAccountControl 512 -Separator ', ' Outputs: "NORMAL_ACCOUNT" #> [cmdletBinding()] param( [alias('UAC')][int] $UserAccountControl, [string] $Separator ) $UserAccount = [ordered] @{ "SCRIPT" = 1 "ACCOUNTDISABLE" = 2 "HOMEDIR_REQUIRED" = 8 "LOCKOUT" = 16 "PASSWD_NOTREQD" = 32 "ENCRYPTED_TEXT_PWD_ALLOWED" = 128 "TEMP_DUPLICATE_ACCOUNT" = 256 "NORMAL_ACCOUNT" = 512 "INTERDOMAIN_TRUST_ACCOUNT" = 2048 "WORKSTATION_TRUST_ACCOUNT" = 4096 "SERVER_TRUST_ACCOUNT" = 8192 "DONT_EXPIRE_PASSWORD" = 65536 "MNS_LOGON_ACCOUNT" = 131072 "SMARTCARD_REQUIRED" = 262144 "TRUSTED_FOR_DELEGATION" = 524288 "NOT_DELEGATED" = 1048576 "USE_DES_KEY_ONLY" = 2097152 "DONT_REQ_PREAUTH" = 4194304 "PASSWORD_EXPIRED" = 8388608 "TRUSTED_TO_AUTH_FOR_DELEGATION" = 16777216 "PARTIAL_SECRETS_ACCOUNT" = 67108864 } $Output = foreach ($_ in $UserAccount.Keys) { $binaryAnd = $UserAccount[$_] -band $UserAccountControl if ($binaryAnd -ne "0") { $_ } } if ($Separator) { $Output -join $Separator } else { $Output } } function ConvertFrom-DistinguishedName { <# .SYNOPSIS Converts a Distinguished Name to CN, OU, Multiple OUs or DC .DESCRIPTION Converts a Distinguished Name to CN, OU, Multiple OUs or DC .PARAMETER DistinguishedName Distinguished Name to convert .PARAMETER ToOrganizationalUnit Converts DistinguishedName to Organizational Unit .PARAMETER ToDC Converts DistinguishedName to DC .PARAMETER ToDomainCN Converts DistinguishedName to Domain Canonical Name (CN) .PARAMETER ToCanonicalName Converts DistinguishedName to Canonical Name .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName -ToOrganizationalUnit Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $DistinguishedName = 'CN=Przemyslaw Klys,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' ConvertFrom-DistinguishedName -DistinguishedName $DistinguishedName Output: Przemyslaw Klys .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit -IncludeParent Output: OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToMultipleOrganizationalUnit Output: OU=Production,DC=ad,DC=evotec,DC=xyz .EXAMPLE $Con = @( 'CN=Windows Authorization Access Group,CN=Builtin,DC=ad,DC=evotec,DC=xyz' 'CN=Mmm,DC=elo,CN=nee,DC=RootDNSServers,CN=MicrosoftDNS,CN=System,DC=ad,DC=evotec,DC=xyz' 'CN=e6d5fd00-385d-4e65-b02d-9da3493ed850,CN=Operations,CN=DomainUpdates,CN=System,DC=ad,DC=evotec,DC=xyz' 'OU=Domain Controllers,DC=ad,DC=evotec,DC=pl' 'OU=Microsoft Exchange Security Groups,DC=ad,DC=evotec,DC=xyz' ) ConvertFrom-DistinguishedName -DistinguishedName $Con -ToLastName Output: Windows Authorization Access Group Mmm e6d5fd00-385d-4e65-b02d-9da3493ed850 Domain Controllers Microsoft Exchange Security Groups .EXAMPLEE ConvertFrom-DistinguishedName -DistinguishedName 'DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName ConvertFrom-DistinguishedName -DistinguishedName 'CN=test,OU=Users,OU=Production,DC=ad,DC=evotec,DC=xyz' -ToCanonicalName Output: ad.evotec.xyz ad.evotec.xyz\Production\Users ad.evotec.xyz\Production\Users\test .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(ParameterSetName = 'ToOrganizationalUnit')] [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')] [Parameter(ParameterSetName = 'ToDC')] [Parameter(ParameterSetName = 'ToDomainCN')] [Parameter(ParameterSetName = 'Default')] [Parameter(ParameterSetName = 'ToLastName')] [Parameter(ParameterSetName = 'ToCanonicalName')] [alias('Identity', 'DN')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)][string[]] $DistinguishedName, [Parameter(ParameterSetName = 'ToOrganizationalUnit')][switch] $ToOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][alias('ToMultipleOU')][switch] $ToMultipleOrganizationalUnit, [Parameter(ParameterSetName = 'ToMultipleOrganizationalUnit')][switch] $IncludeParent, [Parameter(ParameterSetName = 'ToDC')][switch] $ToDC, [Parameter(ParameterSetName = 'ToDomainCN')][switch] $ToDomainCN, [Parameter(ParameterSetName = 'ToLastName')][switch] $ToLastName, [Parameter(ParameterSetName = 'ToCanonicalName')][switch] $ToCanonicalName ) Process { foreach ($Distinguished in $DistinguishedName) { if ($ToDomainCN) { $DN = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' $CN = $DN -replace ',DC=', '.' -replace "DC=" if ($CN) { $CN } } elseif ($ToOrganizationalUnit) { $Value = [Regex]::Match($Distinguished, '(?=OU=)(.*\n?)(?<=.)').Value if ($Value) { $Value } } elseif ($ToMultipleOrganizationalUnit) { if ($IncludeParent) { $Distinguished } while ($true) { $Distinguished = $Distinguished -replace '^.+?,(?=..=)' if ($Distinguished -match '^DC=') { break } $Distinguished } } elseif ($ToDC) { $Value = $Distinguished -replace '.*?((DC=[^=]+,)+DC=[^=]+)$', '$1' if ($Value) { $Value } } elseif ($ToLastName) { $NewDN = $Distinguished -split ",DC=" if ($NewDN[0].Contains(",OU=")) { [Array] $ChangedDN = $NewDN[0] -split ",OU=" } elseif ($NewDN[0].Contains(",CN=")) { [Array] $ChangedDN = $NewDN[0] -split ",CN=" } else { [Array] $ChangedDN = $NewDN[0] } if ($ChangedDN[0].StartsWith('CN=')) { $ChangedDN[0] -replace 'CN=', '' } else { $ChangedDN[0] -replace 'OU=', '' } } elseif ($ToCanonicalName) { $Domain = $null $Rest = $null foreach ($O in $Distinguished -split '(?<!\\),') { if ($O -match '^DC=') { $Domain += $O.Substring(3) + '.' } else { $Rest = $O.Substring(3) + '\' + $Rest } } if ($Domain -and $Rest) { $Domain.Trim('.') + '\' + ($Rest.TrimEnd('\') -replace '\\,', ',') } elseif ($Domain) { $Domain.Trim('.') } elseif ($Rest) { $Rest.TrimEnd('\') -replace '\\,', ',' } } else { $Regex = '^CN=(?<cn>.+?)(?<!\\),(?<ou>(?:(?:OU|CN).+?(?<!\\),)+(?<dc>DC.+?))$' $Found = $Distinguished -match $Regex if ($Found) { $Matches.cn } } } } } function ConvertFrom-ErrorRecord { <# .SYNOPSIS Converts error records into a custom object with selected properties. .DESCRIPTION This function takes error records as input and converts them into a custom object with selected properties, making error records more readable. .PARAMETER ErrorRecord The error record(s) to convert. This parameter is mandatory when the input is an error record. .PARAMETER Exception The special stop exception raised by cmdlets with -ErrorAction Stop. This parameter is mandatory when the input is a stop exception. .EXAMPLE Get-ChildItem -Path 'C:\NonExistentFolder' -ErrorAction Stop | ConvertFrom-ErrorRecord This example will convert the error record generated by attempting to access a non-existent folder into a custom object with properties like Exception message, Reason, Target, Script name, Line number, and Column offset. .EXAMPLE $error[0] | ConvertFrom-ErrorRecord This example will convert the first error record in the $error automatic variable into a custom object with selected properties. #> param ( # we receive either a legit error record... [Management.Automation.ErrorRecord[]] [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'ErrorRecord')] $ErrorRecord, # ...or a special stop exception which is raised by # cmdlets with -ErrorAction Stop [Management.Automation.ActionPreferenceStopException[]] [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'StopException')] $Exception ) process { if ($PSCmdlet.ParameterSetName -eq 'StopException') { $ErrorRecord = $Exception.ErrorRecord } $ErrorRecord | ForEach-Object { [PSCustomObject]@{ Exception = $_.Exception.Message Reason = $_.CategoryInfo.Reason Target = $_.CategoryInfo.TargetName Script = $_.InvocationInfo.ScriptName Line = $_.InvocationInfo.ScriptLineNumber Column = $_.InvocationInfo.OffsetInLine } } } } function ConvertFrom-LanguageCode { <# .SYNOPSIS Converts a language code to its corresponding language name. .DESCRIPTION This function takes a language code as input and returns the corresponding language name. .PARAMETER LanguageCode The language code to convert to a language name. .EXAMPLE ConvertFrom-LanguageCode -LanguageCode 1033 Returns: "English (United States)" .EXAMPLE ConvertFrom-LanguageCode -LanguageCode 1041 Returns: "Japanese" #> [cmdletBinding()] param( [string] $LanguageCode ) $LanguageCodeDictionary = @{ '1' = "Arabic" '4' = "Chinese (Simplified)?? China" '9' = "English" '1025' = "Arabic (Saudi Arabia)" '1026' = "Bulgarian" '1027' = "Catalan" '1028' = "Chinese (Traditional) Taiwan" '1029' = "Czech" '1030' = "Danish" '1031' = "German (Germany)" '1032' = "Greek" '1033' = "English (United States)" '1034' = "Spanish (Traditional Sort)" '1035' = "Finnish" '1036' = "French (France)" '1037' = "Hebrew" '1038' = "Hungarian" '1039' = "Icelandic" '1040' = "Italian (Italy)" '1041' = "Japanese" '1042' = "Korean" '1043' = "Dutch (Netherlands)" '1044' = "Norwegian (Bokmal)" '1045' = "Polish" '1046' = "Portuguese (Brazil)" '1047' = "Rhaeto-Romanic" '1048' = "Romanian" '1049' = "Russian" '1050' = "Croatian" '1051' = "Slovak" '1052' = "Albanian" '1053' = "Swedish" '1054' = "Thai" '1055' = "Turkish" '1056' = "Urdu" '1057' = "Indonesian" '1058' = "Ukrainian" '1059' = "Belarusian" '1060' = "Slovenian" '1061' = "Estonian" '1062' = "Latvian" '1063' = "Lithuanian" '1065' = "Persian" '1066' = "Vietnamese" '1069' = "Basque (Basque)" '1070' = "Serbian" '1071' = "Macedonian (FYROM)" '1072' = "Sutu" '1073' = "Tsonga" '1074' = "Tswana" '1076' = "Xhosa" '1077' = "Zulu" '1078' = "Afrikaans" '1080' = "Faeroese" '1081' = "Hindi" '1082' = "Maltese" '1084' = "Scottish Gaelic (United Kingdom)" '1085' = "Yiddish" '1086' = "Malay (Malaysia)" '2049' = "Arabic (Iraq)" '2052' = "Chinese (Simplified) PRC" '2055' = "German (Switzerland)" '2057' = "English (United Kingdom)" '2058' = "Spanish (Mexico)" '2060' = "French (Belgium)" '2064' = "Italian (Switzerland)" '2067' = "Dutch (Belgium)" '2068' = "Norwegian (Nynorsk)" '2070' = "Portuguese (Portugal)" '2072' = "Romanian (Moldova)" '2073' = "Russian (Moldova)" '2074' = "Serbian (Latin)" '2077' = "Swedish (Finland)" '3073' = "Arabic (Egypt)" '3076' = "Chinese Traditional (Hong Kong SAR)" '3079' = "German (Austria)" '3081' = "English (Australia)" '3082' = "Spanish (International Sort)" '3084' = "French (Canada)" '3098' = "Serbian (Cyrillic)" '4097' = "Arabic (Libya)" '4100' = "Chinese Simplified (Singapore)" '4103' = "German (Luxembourg)" '4105' = "English (Canada)" '4106' = "Spanish (Guatemala)" '4108' = "French (Switzerland)" '5121' = "Arabic (Algeria)" '5127' = "German (Liechtenstein)" '5129' = "English (New Zealand)" '5130' = "Spanish (Costa Rica)" '5132' = "French (Luxembourg)" '6145' = "Arabic (Morocco)" '6153' = "English (Ireland)" '6154' = "Spanish (Panama)" '7169' = "Arabic (Tunisia)" '7177' = "English (South Africa)" '7178' = "Spanish (Dominican Republic)" '8193' = "Arabic (Oman)" '8201' = "English (Jamaica)" '8202' = "Spanish (Venezuela)" '9217' = "Arabic (Yemen)" '9226' = "Spanish (Colombia)" '10241' = "Arabic (Syria)" '10249' = "English (Belize)" '10250' = "Spanish (Peru)" '11265' = "Arabic (Jordan)" '11273' = "English (Trinidad)" '11274' = "Spanish (Argentina)" '12289' = "Arabic (Lebanon)" '12298' = "Spanish (Ecuador)" '13313' = "Arabic (Kuwait)" '13322' = "Spanish (Chile)" '14337' = "Arabic (U.A.E.)" '14346' = "Spanish (Uruguay)" '15361' = "Arabic (Bahrain)" '15370' = "Spanish (Paraguay)" '16385' = "Arabic (Qatar)" '16394' = "Spanish (Bolivia)" '17418' = "Spanish (El Salvador)" '18442' = "Spanish (Honduras)" '19466' = "Spanish (Nicaragua)" '20490' = "Spanish (Puerto Rico)" } $Output = $LanguageCodeDictionary[$LanguageCode] if ($Output) { $Output } else { "Unknown (Undocumented)" } } function ConvertFrom-NetbiosName { <# .SYNOPSIS Converts a NetBIOS name to its corresponding domain name and object name. .DESCRIPTION This function takes a NetBIOS name in the format 'Domain\Object' and converts it to the corresponding domain name and object name. .PARAMETER Identity Specifies the NetBIOS name(s) to convert. .EXAMPLE 'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins' | ConvertFrom-NetbiosName Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names. .EXAMPLE ConvertFrom-NetbiosName -Identity 'TEST\Domain Admins', 'EVOTEC\Domain Admins', 'EVOTECPL\Domain Admins' Converts the NetBIOS names 'TEST\Domain Admins', 'EVOTEC\Domain Admins', and 'EVOTECPL\Domain Admins' to their corresponding domain names and object names. #> [cmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0)] [string[]] $Identity ) process { foreach ($Ident in $Identity) { if ($Ident -like '*\*') { $NetbiosWithObject = $Ident -split "\\" if ($NetbiosWithObject.Count -eq 2) { $LDAPQuery = ([ADSI]"LDAP://$($NetbiosWithObject[0])") $DomainName = ConvertFrom-DistinguishedName -DistinguishedName $LDAPQuery.distinguishedName -ToDomainCN [PSCustomObject] @{ DomainName = $DomainName Name = $NetbiosWithObject[1] } } else { [PSCustomObject] @{ DomainName = '' Name = $Ident } } } else { [PSCustomObject] @{ DomainName = '' Name = $Ident } } } } } function ConvertFrom-ObjectToString { <# .SYNOPSIS Helps with converting given objects to their string representation. .DESCRIPTION Helps with converting given objects to their string representation. .PARAMETER Objects Objects to convert to string representation. .PARAMETER IncludeProperties Properties to include in the string representation. .PARAMETER ExcludeProperties Properties to exclude from the string representation. .PARAMETER OutputType Type of the output object. Options are: Hashtable, Ordered, PSCustomObject. If not specified, the output type is hashtable (string) .PARAMETER NumbersAsString If specified, numbers are converted to strings. Default is number are presented in their (unquoted) numerica form .PARAMETER QuotePropertyNames If specified, all property names are quoted. Default: property names are quoted only if they contain spaces. .PARAMETER DateTimeFormat Format for DateTime values. Default: 'yyyy-MM-dd HH:mm:ss' .EXAMPLE Get-Process -Name "PowerShell" | ConvertFrom-ObjectToString -IncludeProperties 'ProcessName', 'Id', 'Handles' OUTPUT: @{ 'Handles' = '543' 'Id' = '8092' 'ProcessName' = 'powershell' } @{ 'Handles' = '636' 'Id' = '11360' 'ProcessName' = 'powershell' } .NOTES General notes #> [CmdletBinding()] param( [parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Mandatory)][Array] $Objects, [string[]] $IncludeProperties, [string[]] $ExcludeProperties, [ValidateSet('Hashtable', 'Ordered', 'PSCustomObject')][string] $OutputType = 'Hashtable', [switch] $NumbersAsString, [switch] $QuotePropertyNames, [string] $DateTimeFormat = 'yyyy-MM-dd HH:mm:ss' ) begin { if ($OutputType -eq 'Hashtable') { $Type = '' } elseif ($OutputType -eq 'Ordered') { $Type = '[Ordered] ' } else { $Type = '[PSCustomObject] ' } } process { filter IsNumeric() { return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } function GetFormattedPair () { # returns 'key' = <valuestring> or just <valuestring> if key is empty # valuestring is either $null, '<string>', or number param ( [string] $Key, [object] $Value ) if ($key -eq '') { $left = '' } elseif ($key -match '\s' -or $QuotePropertyNames) { $left = "'$Key' = " } else { $left = "$Key = " } if ($null -eq $value) { "$left`$null" } elseif ($Value -is [System.Collections.IList]) { $arrayStrings = foreach ($element in $Object.$Key) { GetFormattedPair -Key '' -Value $element } "$left@(" + ($arrayStrings -join ', ') + ")" } elseif ($Value -is [System.Collections.IDictionary]) { if ($IncludeProperties -and $Key -notin $IncludeProperties) { return } if ($Key -in $ExcludeProperties) { return } $propertyString = foreach ($Key in $Value.Keys) { GetFormattedPair -Key $key -Value $Value[$key] } "$left@{" + ($propertyString -join '; ') + "}" } elseif ($value -is [DateTime]) { "$left'$($Value.ToString($DateTimeFormat))'" } elseif (($value | IsNumeric) -and -not $NumbersAsString) { "$left$($Value)" } else { "$left'$($Value)'" } } foreach ($Object in $Objects) { if ($Object -is [System.Collections.IDictionary]) { Write-Host Write-Host -Object "$Type@{" foreach ($Key in $Object.Keys) { if ($IncludeProperties -and $Key -notin $IncludeProperties) { continue } if ($Key -in $ExcludeProperties) { continue } Write-Host -Object " $(GetFormattedPair -Key $Key -Value $Object.$Key)" -ForegroundColor Cyan } Write-Host -Object "}" } elseif ($Object -is [Object]) { Write-Host Write-Host -Object "$Type@{" foreach ($Key in $Object.PSObject.Properties.Name) { if ($IncludeProperties -and $Key -notin $IncludeProperties) { continue } if ($Key -in $ExcludeProperties) { continue } Write-Host -Object " $(GetFormattedPair -Key $Key -Value $Object.$Key)" -ForegroundColor Cyan } Write-Host -Object "}" } else { Write-Host -Object $Object } } } } Function ConvertFrom-OperationType { <# .SYNOPSIS Converts operation type codes to human-readable descriptions. .DESCRIPTION This function takes an operation type code and returns the corresponding human-readable description. .PARAMETER OperationType The operation type code to be converted. .EXAMPLE ConvertFrom-OperationType -OperationType '%%14674' Output: 'Value Added' .EXAMPLE ConvertFrom-OperationType -OperationType '%%14675' Output: 'Value Deleted' .EXAMPLE ConvertFrom-OperationType -OperationType '%%14676' Output: 'Unknown' #> param ( [string] $OperationType ) $Known = @{ '%%14674' = 'Value Added' '%%14675' = 'Value Deleted' '%%14676' = 'Unknown' } foreach ($id in $OperationType) { if ($name = $Known[$id]) { return $name } } return $OperationType } function ConvertFrom-ScriptBlock { <# .SYNOPSIS Converts a ScriptBlock into an array of strings, each representing a line of the script block. .DESCRIPTION This function takes a ScriptBlock as input and converts it into an array of strings, where each string represents a line of the script block. .PARAMETER ScriptBlock The ScriptBlock to be converted into an array of strings. .EXAMPLE ConvertFrom-ScriptBlock -ScriptBlock { $Variable1 = "Value1" $Variable2 = "Value2" Write-Host "Hello, World!" } This example will output an array containing the following strings: $Variable1 = "Value1" $Variable2 = "Value2" Write-Host "Hello, World!" .NOTES General notes #> [CmdletBinding()] param( [ScriptBlock] $ScriptBlock ) [Array] $Output = foreach ($Line in $ScriptBlock.Ast.EndBlock.Statements.Extent) { [string] $Line + [System.Environment]::NewLine } return $Output } function ConvertFrom-SID { <# .SYNOPSIS Small command that can resolve SID values .DESCRIPTION Small command that can resolve SID values .PARAMETER SID Value to resolve .PARAMETER OnlyWellKnown Only resolve SID when it's well know SID. Otherwise return $null .PARAMETER OnlyWellKnownAdministrative Only resolve SID when it's administrative well know SID. Otherwise return $null .PARAMETER DoNotResolve Uses only dicrionary values without querying AD .EXAMPLE ConvertFrom-SID -SID 'S-1-5-8', 'S-1-5-9', 'S-1-5-11', 'S-1-5-18', 'S-1-1-0' -DoNotResolve .NOTES General notes #> [cmdletbinding(DefaultParameterSetName = 'Standard')] param( [Parameter(ParameterSetName = 'Standard')] [Parameter(ParameterSetName = 'OnlyWellKnown')] [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')] [string[]] $SID, [Parameter(ParameterSetName = 'OnlyWellKnown')][switch] $OnlyWellKnown, [Parameter(ParameterSetName = 'OnlyWellKnownAdministrative')][switch] $OnlyWellKnownAdministrative, [Parameter(ParameterSetName = 'Standard')][switch] $DoNotResolve ) $WellKnownAdministrative = @{ 'S-1-5-18' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } } $wellKnownSIDs = @{ 'S-1-0' = [PSCustomObject] @{ Name = 'Null AUTHORITY' SID = 'S-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-0-0' = [PSCustomObject] @{ Name = 'NULL SID' SID = 'S-1-0-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1' = [PSCustomObject] @{ Name = 'WORLD AUTHORITY' SID = 'S-1-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-1-0' = [PSCustomObject] @{ Name = 'Everyone' SID = 'S-1-1-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2' = [PSCustomObject] @{ Name = 'LOCAL AUTHORITY' SID = 'S-1-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-0' = [PSCustomObject] @{ Name = 'LOCAL' SID = 'S-1-2-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-2-1' = [PSCustomObject] @{ Name = 'CONSOLE LOGON' SID = 'S-1-2-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3' = [PSCustomObject] @{ Name = 'CREATOR AUTHORITY' SID = 'S-1-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-0' = [PSCustomObject] @{ Name = 'CREATOR OWNER' SID = 'S-1-3-0' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-3-1' = [PSCustomObject] @{ Name = 'CREATOR GROUP' SID = 'S-1-3-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-2' = [PSCustomObject] @{ Name = 'CREATOR OWNER SERVER' SID = 'S-1-3-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-3' = [PSCustomObject] @{ Name = 'CREATOR GROUP SERVER' SID = 'S-1-3-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-3-4' = [PSCustomObject] @{ Name = 'OWNER RIGHTS' SID = 'S-1-3-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-0' = [PSCustomObject] @{ Name = 'NT SERVICE\ALL SERVICES' SID = 'S-1-5-80-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-4' = [PSCustomObject] @{ Name = 'Non-unique Authority' SID = 'S-1-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5' = [PSCustomObject] @{ Name = 'NT AUTHORITY' SID = 'S-1-5' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-1' = [PSCustomObject] @{ Name = 'NT AUTHORITY\DIALUP' SID = 'S-1-5-1' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-2' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NETWORK' SID = 'S-1-5-2' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-3' = [PSCustomObject] @{ Name = 'NT AUTHORITY\BATCH' SID = 'S-1-5-3' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-4' = [PSCustomObject] @{ Name = 'NT AUTHORITY\INTERACTIVE' SID = 'S-1-5-4' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-6' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SERVICE' SID = 'S-1-5-6' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-7' = [PSCustomObject] @{ Name = 'NT AUTHORITY\ANONYMOUS LOGON' SID = 'S-1-5-7' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-8' = [PSCustomObject] @{ Name = 'NT AUTHORITY\PROXY' SID = 'S-1-5-8' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-9' = [PSCustomObject] @{ Name = 'NT AUTHORITY\ENTERPRISE DOMAIN CONTROLLERS' SID = 'S-1-5-9' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-10' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SELF' SID = 'S-1-5-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-11' = [PSCustomObject] @{ Name = 'NT AUTHORITY\Authenticated Users' SID = 'S-1-5-11' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-12' = [PSCustomObject] @{ Name = 'NT AUTHORITY\RESTRICTED' SID = 'S-1-5-12' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-13' = [PSCustomObject] @{ Name = 'NT AUTHORITY\TERMINAL SERVER USER' SID = 'S-1-5-13' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-14' = [PSCustomObject] @{ Name = 'NT AUTHORITY\REMOTE INTERACTIVE LOGON' SID = 'S-1-5-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-15' = [PSCustomObject] @{ Name = 'NT AUTHORITY\This Organization' SID = 'S-1-5-15' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-17' = [PSCustomObject] @{ Name = 'NT AUTHORITY\IUSR' SID = 'S-1-5-17' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-18' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SYSTEM' SID = 'S-1-5-18' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-19' = [PSCustomObject] @{ Name = 'NT AUTHORITY\LOCAL SERVICE' SID = 'S-1-5-19' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-20' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NETWORK SERVICE' SID = 'S-1-5-20' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-544' = [PSCustomObject] @{ Name = 'BUILTIN\Administrators' SID = 'S-1-5-32-544' DomainName = '' Type = 'WellKnownAdministrative' Error = '' } 'S-1-5-32-545' = [PSCustomObject] @{ Name = 'BUILTIN\Users' SID = 'S-1-5-32-545' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-546' = [PSCustomObject] @{ Name = 'BUILTIN\Guests' SID = 'S-1-5-32-546' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-547' = [PSCustomObject] @{ Name = 'BUILTIN\Power Users' SID = 'S-1-5-32-547' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-548' = [PSCustomObject] @{ Name = 'BUILTIN\Account Operators' SID = 'S-1-5-32-548' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-549' = [PSCustomObject] @{ Name = 'BUILTIN\Server Operators' SID = 'S-1-5-32-549' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-550' = [PSCustomObject] @{ Name = 'BUILTIN\Print Operators' SID = 'S-1-5-32-550' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-551' = [PSCustomObject] @{ Name = 'BUILTIN\Backup Operators' SID = 'S-1-5-32-551' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-552' = [PSCustomObject] @{ Name = 'BUILTIN\Replicators' SID = 'S-1-5-32-552' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-10' = [PSCustomObject] @{ Name = 'NT AUTHORITY\NTLM Authentication' SID = 'S-1-5-64-10' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-14' = [PSCustomObject] @{ Name = 'NT AUTHORITY\SChannel Authentication' SID = 'S-1-5-64-14' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-64-21' = [PSCustomObject] @{ Name = 'NT AUTHORITY\Digest Authentication' SID = 'S-1-5-64-21' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80' = [PSCustomObject] @{ Name = 'NT SERVICE' SID = 'S-1-5-80' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-83-0' = [PSCustomObject] @{ Name = 'NT VIRTUAL MACHINE\Virtual Machines' SID = 'S-1-5-83-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-0' = [PSCustomObject] @{ Name = 'Untrusted Mandatory Level' SID = 'S-1-16-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-4096' = [PSCustomObject] @{ Name = 'Low Mandatory Level' SID = 'S-1-16-4096' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8192' = [PSCustomObject] @{ Name = 'Medium Mandatory Level' SID = 'S-1-16-8192' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-8448' = [PSCustomObject] @{ Name = 'Medium Plus Mandatory Level' SID = 'S-1-16-8448' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-12288' = [PSCustomObject] @{ Name = 'High Mandatory Level' SID = 'S-1-16-12288' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-16384' = [PSCustomObject] @{ Name = 'System Mandatory Level' SID = 'S-1-16-16384' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-20480' = [PSCustomObject] @{ Name = 'Protected Process Mandatory Level' SID = 'S-1-16-20480' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-16-28672' = [PSCustomObject] @{ Name = 'Secure Process Mandatory Level' SID = 'S-1-16-28672' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-554' = [PSCustomObject] @{ Name = 'BUILTIN\Pre-Windows 2000 Compatible Access' SID = 'S-1-5-32-554' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-555' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Desktop Users' SID = 'S-1-5-32-555' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-556' = [PSCustomObject] @{ Name = 'BUILTIN\Network Configuration Operators' SID = 'S-1-5-32-556' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-557' = [PSCustomObject] @{ Name = 'BUILTIN\Incoming Forest Trust Builders' SID = 'S-1-5-32-557' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-558' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Monitor Users' SID = 'S-1-5-32-558' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-559' = [PSCustomObject] @{ Name = 'BUILTIN\Performance Log Users' SID = 'S-1-5-32-559' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-560' = [PSCustomObject] @{ Name = 'BUILTIN\Windows Authorization Access Group' SID = 'S-1-5-32-560' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-561' = [PSCustomObject] @{ Name = 'BUILTIN\Terminal Server License Servers' SID = 'S-1-5-32-561' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-562' = [PSCustomObject] @{ Name = 'BUILTIN\Distributed COM Users' SID = 'S-1-5-32-562' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-568' = [PSCustomObject] @{ Name = 'BUILTIN\IIS_IUSRS' SID = 'S-1-5-32-568' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-569' = [PSCustomObject] @{ Name = 'BUILTIN\Cryptographic Operators' SID = 'S-1-5-32-569' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-573' = [PSCustomObject] @{ Name = 'BUILTIN\Event Log Readers' SID = 'S-1-5-32-573' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-574' = [PSCustomObject] @{ Name = 'BUILTIN\Certificate Service DCOM Access' SID = 'S-1-5-32-574' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-575' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Remote Access Servers' SID = 'S-1-5-32-575' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-576' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Endpoint Servers' SID = 'S-1-5-32-576' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-577' = [PSCustomObject] @{ Name = 'BUILTIN\RDS Management Servers' SID = 'S-1-5-32-577' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-578' = [PSCustomObject] @{ Name = 'BUILTIN\Hyper-V Administrators' SID = 'S-1-5-32-578' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-579' = [PSCustomObject] @{ Name = 'BUILTIN\Access Control Assistance Operators' SID = 'S-1-5-32-579' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-32-580' = [PSCustomObject] @{ Name = 'BUILTIN\Remote Management Users' SID = 'S-1-5-32-580' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-90-0' = [PSCustomObject] @{ Name = 'Window Manager\Window Manager Group' SID = 'S-1-5-90-0' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' = [PSCustomObject] @{ Name = 'NT SERVICE\WdiServiceHost' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3880718306-3832830129-1677859214-2598158968-1052248003' = [PSCustomObject] @{ Name = 'NT SERVICE\MSSQLSERVER' SID = 'S-1-5-80-3139157870-2983391045-3678747466-658725712-1809340420' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLSERVERAGENT' SID = 'S-1-5-80-344959196-2060754871-2302487193-2804545603-1466107430' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' = [PSCustomObject] @{ Name = 'NT SERVICE\SQLTELEMETRY' SID = 'S-1-5-80-2652535364-2169709536-2857650723-2622804123-1107741775' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' = [PSCustomObject] @{ Name = 'NT SERVICE\ADSync' SID = 'S-1-5-80-3245704983-3664226991-764670653-2504430226-901976451' DomainName = '' Type = 'WellKnownGroup' Error = '' } 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' = [PSCustomObject] @{ Name = 'NT Service\himds' SID = 'S-1-5-80-4215458991-2034252225-2287069555-1155419622-2701885083' DomainName = '' Type = 'WellKnownGroup' Error = '' } } foreach ($S in $SID) { if ($OnlyWellKnownAdministrative) { if ($WellKnownAdministrative[$S]) { $WellKnownAdministrative[$S] } } elseif ($OnlyWellKnown) { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } } else { if ($wellKnownSIDs[$S]) { $wellKnownSIDs[$S] } else { if ($DoNotResolve) { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Type = 'Administrative' Error = '' } } else { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Error = '' Type = 'NotAdministrative' } } } else { if (-not $Script:LocalComputerSID) { $Script:LocalComputerSID = Get-LocalComputerSid } try { if ($S.Length -le 18) { $Type = 'NotAdministrative' $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{ Name = $Name SID = $S DomainName = '' Type = $Type Error = '' } } else { if ($S -like "S-1-5-21-*-519" -or $S -like "S-1-5-21-*-512" -or $S -like "S-1-5-21-*-518") { $Type = 'Administrative' } else { $Type = 'NotAdministrative' } $Name = (([System.Security.Principal.SecurityIdentifier]::new($S)).Translate([System.Security.Principal.NTAccount])).Value [PSCustomObject] @{ Name = $Name SID = $S DomainName = if ($S -like "$Script:LocalComputerSID*") { '' } else { (ConvertFrom-NetbiosName -Identity $Name).DomainName } Type = $Type Error = '' } } } catch { [PSCustomObject] @{ Name = $S SID = $S DomainName = '' Error = $_.Exception.Message -replace [environment]::NewLine, ' ' Type = 'Unknown' } } } } } } } function ConvertFrom-X500Address { <# .SYNOPSIS Converts an X500 address to a readable email address. .DESCRIPTION This function converts an X500 address to a readable email address by removing unnecessary characters and formatting it properly. .PARAMETER IMCEAEXString The X500 address string to be converted. .PARAMETER Full Indicates whether to return the full email address or just the username part. .EXAMPLE By default returns string without @evotec.pl in the end. This is because the string from NDR needs domain name removed to be able to add it back to Exchange ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=5209048016da47689b4421790ad1763f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=8bcad655e07c46788fe1f796162cd87f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' .EXAMPLE ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=5209048016da47689b4421790ad1763f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' -Full ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=8bcad655e07c46788fe1f796162cd87f-EVOTEC+20PL+20Recepcja+20G@evotec.pl' -Full ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' -Full ConvertFrom-X500Address -IMCEAEXString 'IMCEAEX-_o=AD_ou=Exchange+20Administrative+20Group+20+28FYDIBOHF23SPDLT+29_cn=Recipients_cn=0d4540e9a8f845d798625c9c0ad753bf-Test-All-Group@evotec.pl' -Full #> param( [string] $IMCEAEXString, [switch] $Full ) $Final = $IMCEAEXString.Replace("IMCEAEX-", "").Replace("_", "/").Replace("+20", " ").Replace("+28", "(").Replace("+29", ")").Replace("+2E", ".").Replace("+2C", ",").Replace("+5F", "_") if ($Full) { return $Final } else { return ($Final -split '@')[0] } } function ConvertTo-DistinguishedName { <# .SYNOPSIS Converts CanonicalName to DistinguishedName .DESCRIPTION Converts CanonicalName to DistinguishedName for 3 different options .PARAMETER CanonicalName One or multiple canonical names .PARAMETER ToOU Converts CanonicalName to OrganizationalUnit DistinguishedName .PARAMETER ToObject Converts CanonicalName to Full Object DistinguishedName .PARAMETER ToDomain Converts CanonicalName to Domain DistinguishedName .EXAMPLE $CanonicalObjects = @( 'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins' 'ad.evotec.xyz/Production/Accounts/Special/SADM Testing 2' ) $CanonicalOU = @( 'ad.evotec.xyz/Production/Groups/Security/NetworkAdministration' 'ad.evotec.xyz/Production' ) $CanonicalDomain = @( 'ad.evotec.xyz/Production/Groups/Security/ITR03_AD Admins' 'ad.evotec.pl' 'ad.evotec.xyz' 'test.evotec.pl' 'ad.evotec.xyz/Production' ) $CanonicalObjects | ConvertTo-DistinguishedName -ToObject $CanonicalOU | ConvertTo-DistinguishedName -ToOU $CanonicalDomain | ConvertTo-DistinguishedName -ToDomain Output: CN=ITR03_AD Admins,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz CN=SADM Testing 2,OU=Special,OU=Accounts,OU=Production,DC=ad,DC=evotec,DC=xyz Output2: OU=NetworkAdministration,OU=Security,OU=Groups,OU=Production,DC=ad,DC=evotec,DC=xyz OU=Production,DC=ad,DC=evotec,DC=xyz Output3: DC=ad,DC=evotec,DC=xyz DC=ad,DC=evotec,DC=pl DC=ad,DC=evotec,DC=xyz DC=test,DC=evotec,DC=pl DC=ad,DC=evotec,DC=xyz .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'ToDomain')] param( [Parameter(ParameterSetName = 'ToOU')] [Parameter(ParameterSetName = 'ToObject')] [Parameter(ParameterSetName = 'ToDomain')] [alias('Identity', 'CN')][Parameter(ValueFromPipeline, Mandatory, ValueFromPipelineByPropertyName, Position = 0)][string[]] $CanonicalName, [Parameter(ParameterSetName = 'ToOU')][switch] $ToOU, [Parameter(ParameterSetName = 'ToObject')][switch] $ToObject, [Parameter(ParameterSetName = 'ToDomain')][switch] $ToDomain ) Process { foreach ($CN in $CanonicalName) { if ($ToObject) { $ADObject = $CN.Replace(',', '\,').Split('/') [string]$DN = "CN=" + $ADObject[$ADObject.count - 1] for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] } $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ } } elseif ($ToOU) { $ADObject = $CN.Replace(',', '\,').Split('/') [string]$DN = "OU=" + $ADObject[$ADObject.count - 1] for ($i = $ADObject.count - 2; $i -ge 1; $i--) { $DN += ",OU=" + $ADObject[$i] } $ADObject[0].split(".") | ForEach-Object { $DN += ",DC=" + $_ } } else { $ADObject = $CN.Replace(',', '\,').Split('/') $DN = 'DC=' + $ADObject[0].Replace('.', ',DC=') } $DN } } } function ConvertTo-FlatHashtable { <# .SYNOPSIS Converts nested hashtable into flat hashtable using delimiter .DESCRIPTION Converts nested hashtable into flat hashtable using delimiter .PARAMETER InputObject Ordered Dictionary or Hashtable .PARAMETER Delimiter Delimiter for key name when merging nested hashtables. By default colon is used .EXAMPLE ConvertTo-FlatHashTable -InputObject ([ordered] @{ RootEntry = 'OK1' Parent = @{ Child1 = 'OK2' Child2 = 'Ok3' } ParentDifferent = @{ Child7 = 'NotOk' Child8 = @{ Child10 = 'OKLetsSee' Child11 = @{ SpecialCase = 'Oooop' } } } }) | Format-Table * .NOTES General notes #> [CmdletBinding()] param( [System.Collections.IDictionary] $InputObject, [string] $Delimiter = ':', [Parameter(DontShow)][string] $Name ) Begin { $Output = [ordered] @{} } Process { foreach ($Key in $InputObject.Keys) { if ($Name) { $MergedName = "$Name$($Delimiter)$Key" } else { $MergedName = $Key } if ($InputObject[$Key] -is [System.Collections.IDictionary]) { $Found = ConvertTo-FlatHashtable -InputObject $InputObject[$Key] -Name $MergedName $Output = $Output + $Found } else { $Output[$MergedName] = $InputObject[$Key] } } } End { $Output } } Function ConvertTo-FlatObject { <# .SYNOPSIS Flattends a nested object into a single level object. .DESCRIPTION Flattends a nested object into a single level object. .PARAMETER Objects The object (or objects) to be flatten. .PARAMETER Separator The separator used between the recursive property names .PARAMETER Base The first index name of an embedded array: - 1, arrays will be 1 based: <Parent>.1, <Parent>.2, <Parent>.3, … - 0, arrays will be 0 based: <Parent>.0, <Parent>.1, <Parent>.2, … - "", the first item in an array will be unnamed and than followed with 1: <Parent>, <Parent>.1, <Parent>.2, … .PARAMETER Depth The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER Uncut The maximal depth of flattening a recursive property. Any negative value will result in an unlimited depth and could cause a infinitive loop. .PARAMETER ExcludeProperty The propertys to be excluded from the output. .EXAMPLE $Object3 = [PSCustomObject] @{ "Name" = "Przemyslaw Klys" "Age" = "30" "Address" = @{ "Street" = "Kwiatowa" "City" = "Warszawa" "Country" = [ordered] @{ "Name" = "Poland" } List = @( [PSCustomObject] @{ "Name" = "Adam Klys" "Age" = "32" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = "33" } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = 30 } [PSCustomObject] @{ "Name" = "Justyna Klys" "Age" = $null } ) } ListTest = @( [PSCustomObject] @{ "Name" = "Sława Klys" "Age" = "33" } ) } $Object3 | ConvertTo-FlatObject .NOTES Based on https://powersnippets.com/convertto-flatobject/ #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeLine)][Object[]]$Objects, [String]$Separator = ".", [ValidateSet("", 0, 1)]$Base = 1, [int]$Depth = 5, [string[]] $ExcludeProperty, [Parameter(DontShow)][String[]]$Path, [Parameter(DontShow)][System.Collections.IDictionary] $OutputObject ) Begin { $InputObjects = [System.Collections.Generic.List[Object]]::new() } Process { foreach ($O in $Objects) { if ($null -ne $O) { $InputObjects.Add($O) } } } End { If ($PSBoundParameters.ContainsKey("OutputObject")) { $Object = $InputObjects[0] $Iterate = [ordered] @{} if ($null -eq $Object) { } elseif ($Object.GetType().Name -in 'String', 'DateTime', 'TimeSpan', 'Version', 'Enum') { $Object = $Object.ToString() } elseif ($Depth) { $Depth-- If ($Object -is [System.Collections.IDictionary]) { $Iterate = $Object } elseif ($Object -is [Array] -or $Object -is [System.Collections.IEnumerable]) { $i = $Base foreach ($Item in $Object.GetEnumerator()) { $NewObject = [ordered] @{} If ($Item -is [System.Collections.IDictionary]) { foreach ($Key in $Item.Keys) { if ($Key -notin $ExcludeProperty) { $NewObject[$Key] = $Item[$Key] } } } elseif ($Item -isnot [Array] -and $Item -isnot [System.Collections.IEnumerable]) { foreach ($Prop in $Item.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $NewObject["$($Prop.Name)"] = $Item.$($Prop.Name) } } } else { $NewObject = $Item } $Iterate["$i"] = $NewObject $i += 1 } } else { foreach ($Prop in $Object.PSObject.Properties) { if ($Prop.IsGettable -and $Prop.Name -notin $ExcludeProperty) { $Iterate["$($Prop.Name)"] = $Object.$($Prop.Name) } } } } If ($Iterate.Keys.Count) { foreach ($Key in $Iterate.Keys) { if ($Key -notin $ExcludeProperty) { ConvertTo-FlatObject -Objects @(, $Iterate["$Key"]) -Separator $Separator -Base $Base -Depth $Depth -Path ($Path + $Key) -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty } } } else { $Property = $Path -Join $Separator if ($Property) { if ($Object -is [System.Collections.IDictionary] -and $Object.Keys.Count -eq 0) { $OutputObject[$Property] = $null } else { $OutputObject[$Property] = $Object } } } } elseif ($InputObjects.Count -gt 0) { foreach ($ItemObject in $InputObjects) { $OutputObject = [ordered]@{} ConvertTo-FlatObject -Objects @(, $ItemObject) -Separator $Separator -Base $Base -Depth $Depth -Path $Path -OutputObject $OutputObject -ExcludeProperty $ExcludeProperty [PSCustomObject] $OutputObject } } } } function ConvertTo-Identity { <# .SYNOPSIS Converts an identity to its corresponding information. .DESCRIPTION This function converts an identity to its corresponding information, such as Name, SID, Type, and Class. It retrieves information from Active Directory based on the provided identity. .PARAMETER Identity Specifies the identity to convert. .PARAMETER ADAdministrativeGroups Specifies the Active Directory administrative groups. .PARAMETER Forest Specifies the forest name. .PARAMETER ExcludeDomains Specifies the domains to exclude. .PARAMETER IncludeDomains Specifies the domains to include. .PARAMETER ExtendedForestInformation Specifies additional information about the forest. .EXAMPLE ConvertTo-Identity -Identity "JohnDoe" -Forest "example.com" -IncludeDomains "domain1", "domain2" -ExcludeDomains "domain3" -ADAdministrativeGroups $ADGroups -ExtendedForestInformation $ExtendedInfo Converts the identity "JohnDoe" in the forest "example.com", including domains "domain1" and "domain2" while excluding "domain3", using the specified administrative groups and extended forest information. .NOTES File Name : ConvertTo-Identity.ps1 Prerequisite : This function requires Active Directory PowerShell module. #> [cmdletBinding()] param( [string] $Identity, [System.Collections.IDictionary] $ADAdministrativeGroups, [alias('ForestName')][string] $Forest, [string[]] $ExcludeDomains, [alias('Domain', 'Domains')][string[]] $IncludeDomains, [System.Collections.IDictionary] $ExtendedForestInformation ) Begin { if (-not $ExtendedForestInformation) { $ForestInformation = Get-WinADForestDetails -Extended -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } else { $ForestInformation = $ExtendedForestInformation } if (-not $ADAdministrativeGroups) { $ADAdministrativeGroups = Get-ADADministrativeGroups -Type DomainAdmins, EnterpriseAdmins -Forest $Forest -IncludeDomains $IncludeDomains -ExcludeDomains $ExcludeDomains -ExtendedForestInformation $ExtendedForestInformation } if (-not $Script:GlobalCacheIdentity) { $Script:GlobalCacheIdentity = @{ } } } Process { $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($Identity)"] if ($AdministrativeGroup) { [PSCustomObject] @{ Name = $Identity SID = $AdministrativeGroup.SID.Value Type = 'Administrative' Class = $AdministrativeGroup.ObjectClass Error = '' } } else { if ($Identity -like '*@*') { Write-Warning "ConvertTo-Identity - Not implemented." } elseif ($Identity -like '*\*') { if ($Script:GlobalCacheIdentity[$Identity]) { $Script:GlobalCacheIdentity[$Identity] } else { $MyIdentity = $Identity.Split("\") $DNSRoot = $ForestInformation['DomainsExtendedNetBIOS'][$($MyIdentity[0])]['DNSRoot'] $QueryServer = $ForestInformation['QueryServers'][$DNSRoot]['HostName'][0] $ADObject = Get-ADObject -Filter "SamAccountName -eq '$($MyIdentity[1])'" -Server $QueryServer -Properties AdminCount, CanonicalName, Name, sAMAccountName, DisplayName, DistinguishedName, ObjectClass, objectSid if ($ADObject) { $Script:GlobalCacheIdentity[$Identity] = [PSCustomObject] @{ Name = $Identity SID = $ADObject.objectSid.Value Type = 'NotAdministrative' Class = $AdObject.ObjectClass Error = '' } $Script:GlobalCacheIdentity[$Identity] } else { [PSCustomObject] @{ Name = $Identity SID = $Identity Type = 'Unknown' Class = 'unknown' Error = 'Object not found.' } } } } elseif ($Identity -like '*-*-*-*') { $Data = ConvertFrom-SID -SID $Identity if ($Data) { if ($Data.Error) { [PSCustomObject] @{ Name = $Data.Name SID = $Data.Sid Type = $Data.Type Class = 'unknown' Error = $Data.Error } } else { $AdministrativeGroup = $ADAdministrativeGroups['ByNetBIOS']["$($Data.Name)"] if ($AdministrativeGroup) { [PSCustomObject] @{ Name = $Data.Name SID = $AdministrativeGroup.SID.Value Type = 'Administrative' Class = $AdministrativeGroup.ObjectClass Error = '' } } else { [PSCustomObject] @{ Name = $Data.Name SID = $Data.Sid Type = $Data.Type Class = '' Error = $Data.Error } } } } else { [PSCustomObject] @{ Name = $Identity SID = $Identity Type = 'Unknown' Class = 'unknown' Error = 'SID not found' } } } else { [PSCustomObject] @{ Name = $Identity SID = $Identity Type = 'Unknown' Class = 'unknown' Error = 'Identity unknown' } } } } End { } } function ConvertTo-ImmutableID { <# .SYNOPSIS Converts an Active Directory user's ObjectGUID to an ImmutableID. .DESCRIPTION This function takes an Active Directory user object or a GUID as input and converts the ObjectGUID to an ImmutableID, which is commonly used in Azure AD. .PARAMETER User Specifies the Active Directory user object to convert. This parameter is mutually exclusive with the 'ObjectGUID' parameter. .PARAMETER ObjectGUID Specifies the GUID to convert to ImmutableID. This parameter is mutually exclusive with the 'User' parameter. .EXAMPLE ConvertTo-ImmutableID -User $ADUser Converts the ObjectGUID of the specified Active Directory user to an ImmutableID. .EXAMPLE ConvertTo-ImmutableID -ObjectGUID "12345678-1234-1234-1234-1234567890AB" Converts the specified GUID to an ImmutableID. #> [CmdletBinding()] param( [Parameter(Mandatory = $false, ParameterSetName = 'User')] [alias('ADuser')] [Microsoft.ActiveDirectory.Management.ADAccount] $User, [Parameter(Mandatory = $false, ParameterSetName = 'Guid')] [alias('GUID')] [string] $ObjectGUID ) if ($User) { if ($User.ObjectGUID) { $ObjectGUID = $User.ObjectGuid } } if ($ObjectGUID) { $ImmutableID = [System.Convert]::ToBase64String(($User.ObjectGUID).ToByteArray()) return $ImmutableID } return } function ConvertTo-JsonLiteral { <# .SYNOPSIS Converts an object to a JSON-formatted string. .DESCRIPTION The ConvertTo-Json cmdlet converts any object to a string in JavaScript Object Notation (JSON) format. The properties are converted to field names, the field values are converted to property values, and the methods are removed. .PARAMETER Object Specifies the objects to convert to JSON format. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe an object to ConvertTo-JsonLiteral .PARAMETER Depth Specifies how many levels of contained objects are included in the JSON representation. The default value is 0. .PARAMETER AsArray Outputs the object in array brackets, even if the input is a single object. .PARAMETER DateTimeFormat Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss" .PARAMETER NumberAsString Provides an alternative serialization option that converts all numbers to their string representation. .PARAMETER BoolAsString Provides an alternative serialization option that converts all bool to their string representation. .PARAMETER PropertyName Uses PropertyNames provided by user (only works with Force) .PARAMETER NewLineFormat Provides a way to configure how new lines are converted for property names .PARAMETER NewLineFormatProperty Provides a way to configure how new lines are converted for values .PARAMETER PropertyName Allows passing property names to be used for custom objects (hashtables and alike are unaffected) .PARAMETER ArrayJoin Forces any array to be a string regardless of depth level .PARAMETER ArrayJoinString Uses defined string or char for array join. By default it uses comma with a space when used. .PARAMETER Force Forces using property names from first object or given thru PropertyName parameter .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -Depth 3 .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" } -NumberAsString -BoolAsString .EXAMPLE Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NumberAsString -BoolAsString -DateTimeFormat "yyyy-MM-dd HH:mm:ss" .EXAMPLE # Keep in mind this advanced replace will break ConvertFrom-Json, but it's sometimes useful for projects like PSWriteHTML Get-Process | Select-Object -First 2 | ConvertTo-JsonLiteral -NewLineFormat $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" } -NumberAsString -BoolAsString -AdvancedReplace @{ '.' = '\.'; '$' = '\$' } .NOTES General notes #> [cmdletBinding()] param( [alias('InputObject')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, Mandatory)][Array] $Object, [int] $Depth, [switch] $AsArray, [string] $DateTimeFormat = "yyyy-MM-dd HH:mm:ss", [switch] $NumberAsString, [switch] $BoolAsString, [System.Collections.IDictionary] $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $NewLineFormatProperty = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $AdvancedReplace, [string] $ArrayJoinString, [switch] $ArrayJoin, [string[]]$PropertyName, [switch] $Force ) Begin { $TextBuilder = [System.Text.StringBuilder]::new() $CountObjects = 0 filter IsNumeric() { return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } filter IsOfType() { return $_ -is [bool] -or $_ -is [char] -or $_ -is [datetime] -or $_ -is [string] ` -or $_ -is [timespan] -or $_ -is [URI] ` -or $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } [int] $MaxDepth = $Depth [int] $InitialDepth = 0 } Process { for ($a = 0; $a -lt $Object.Count; $a++) { $CountObjects++ if ($CountObjects -gt 1) { $null = $TextBuilder.Append(',') } if ($Object[$a] -is [System.Collections.IDictionary]) { $null = $TextBuilder.AppendLine("{") for ($i = 0; $i -lt ($Object[$a].Keys).Count; $i++) { $Property = ([string[]]$Object[$a].Keys)[$i] $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $Value = ConvertTo-StringByType -Value $Object[$a][$Property] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append("$Value") if ($i -ne ($Object[$a].Keys).Count - 1) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } elseif ($Object[$a] | IsOfType) { $Value = ConvertTo-StringByType -Value $Object[$a] -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append($Value) } else { $null = $TextBuilder.AppendLine("{") if ($Force -and -not $PropertyName) { $PropertyName = $Object[0].PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $Object[$a].PSObject.Properties.Name } $PropertyCount = 0 foreach ($Property in $PropertyName) { $PropertyCount++ $DisplayProperty = $Property.Replace('\', "\\").Replace('"', '\"').Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $null = $TextBuilder.Append("`"$DisplayProperty`":") $Value = ConvertTo-StringByType -Value $Object[$a].$Property -DateTimeFormat $DateTimeFormat -NumberAsString:$NumberAsString -BoolAsString:$BoolAsString -Depth $InitialDepth -MaxDepth $MaxDepth -TextBuilder $TextBuilder -NewLineFormat $NewLineFormat -NewLineFormatProperty $NewLineFormatProperty -Force:$Force -ArrayJoin:$ArrayJoin -ArrayJoinString $ArrayJoinString -AdvancedReplace $AdvancedReplace $null = $TextBuilder.Append("$Value") if ($PropertyCount -ne $PropertyName.Count) { $null = $TextBuilder.AppendLine(',') } } $null = $TextBuilder.Append("}") } $InitialDepth = 0 } } End { if ($CountObjects -gt 1 -or $AsArray) { "[$($TextBuilder.ToString())]" } else { $TextBuilder.ToString() } } } function ConvertTo-NormalizedString { <# .SYNOPSIS Converts a string to a normalized string .DESCRIPTION Converts a string to a normalized string .PARAMETER String The string to convert .EXAMPLE ConvertTo-NormalizedString -String "café" .EXAMPLE "café" | ConvertTo-NormalizedString .EXAMPLE ConvertTo-NormalizedString -String "café" "café" | ConvertTo-NormalizedString 'Helène' | ConvertTo-NormalizedString "Przemysław Kłys and Helène" | ConvertTo-NormalizedString .EXAMPLE "äöüß" | ConvertTo-NormalizedString ConvertTo-NormalizedString -String "café" "café" | ConvertTo-NormalizedString 'Helène' | ConvertTo-NormalizedString "Przemysław Kłys and Helène" | ConvertTo-NormalizedString "kłys" | ConvertTo-NormalizedString "ąęćśł" | ConvertTo-NormalizedString "Michael Roßbach" | ConvertTo-NormalizedString "öüóőúéáűí" | ConvertTo-NormalizedString "ß" | ConvertTo-NormalizedString "Un été de Raphaël" | ConvertTo-NormalizedString ("äâûê", "éèà", "ùçä") | ConvertTo-NormalizedString "Fore ðære mærðe…" | ConvertTo-NormalizedString "ABC-abc-ČŠŽ-čšž" | ConvertTo-NormalizedString "Æ×Þ°±ß…" | ConvertTo-NormalizedString .NOTES General notes #># [CmdletBinding()] param( [parameter(ValueFromPipeline)][string[]] $String, [switch] $Simplify ) Begin { $SpecialCasesGerman = @{ [char] "ä" = "a" [char] "ö" = "o" [char] "ü" = "u" [char] "ß" = "ss" [char] "Ö" = "O" [char] "Ü" = "U" [char] "Ä" = "A" } $SpecialCasesGermanGramatical = @{ [char] "ä" = "ae" [char] "ö" = "oe" [char] "ü" = "ue" [char] "ß" = "ss" [char] "Ö" = "Oe" [char] "Ü" = "Ue" [char] "Ä" = "Ae" } $SpecialCases = @{ [char]"ø" = "o" [char]"Ø" = "O" [char]"Å" = "A" [char]'ð' = 'd' [char]'Æ' = 'AE' [char]'æ' = 'ae' [char]'Þ' = 'TH' [char]'þ' = 'th' [char]'×' = 'X' [char]'°' = 'o' [char]'±' = 'p' [char]'ç' = 'c' [char]'Ç' = 'C' [char]"…" = "..." [char]"ï" = "i" [char]"Ï" = "I" [char]"ű" = "u" [char]"ő" = "o" [char]"á" = "a" [char]"é" = "e" [char]"í" = "i" [char]"ó" = "o" [char]"ú" = "u" [char]"ý" = "y" [char]"ĺ" = "l" [char]"ŕ" = "r" [char]"č" = "c" [char]"ď" = "d" [char]"ľ" = "l" [char]"ň" = "n" [char]"š" = "s" [char]"ť" = "t" [char]"ž" = "z" [char]"ô" = "o" [char]"ą" = "a" [char]"ę" = "e" [char]"è" = "e" [char]"ć" = "c" [char]"ś" = "s" [char]"ź" = "z" [char]"ł" = "l" [char]"ń" = "n" [char]"Ű" = "U" [char]"Ő" = "O" [char]"Á" = "A" [char]"É" = "E" [char]"Í" = "I" [char]"Ó" = "O" [char]"Ú" = "U" [char]"Ý" = "Y" [char]"Ĺ" = "L" [char]"Ŕ" = "R" [char]"Č" = "C" [char]"Ď" = "D" [char]"Ľ" = "L" [char]"Ň" = "N" [char]"Š" = "S" [char]"Ť" = "T" [char]"Ž" = "Z" [char]"Ô" = "O" [char]"Ą" = "A" [char]"Ę" = "E" [char]"Ć" = "C" [char]"Ś" = "S" [char]"Ź" = "Z" [char]"Ł" = "L" [char]"Ń" = "N" "_" = " " } } Process { foreach ($S in $String) { $sb = [System.Text.StringBuilder]::new() foreach ($Char in $S.ToCharArray()) { if ($Simplify -and $SpecialCasesGerman.ContainsKey($Char)) { [void] $sb.Append($SpecialCasesGerman[$Char]) } elseif ($SpecialCasesGermanGramatical.ContainsKey($Char)) { [void] $sb.Append($SpecialCasesGermanGramatical[$Char]) } elseif ($SpecialCases.ContainsKey($Char)) { [void] $sb.Append($SpecialCases[$Char]) } else { [void] $sb.Append($Char) } } $S = $sb.ToString() $normalizedString = $S.Normalize([System.Text.NormalizationForm]::FormD) $sb = [System.Text.StringBuilder]::new() for ($i = 0; $i -lt $normalizedString.Length; $i++) { $c = $normalizedString[$i] if ([Globalization.CharUnicodeInfo]::GetUnicodeCategory($c) -ne [Globalization.UnicodeCategory]::NonSpacingMark) { [void]$sb.Append($c) } } $S = $sb.ToString().Normalize([System.Text.NormalizationForm]::FormC) $sb = [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($S)) $sb } } } function ConvertTo-OperatingSystem { <# .SYNOPSIS Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .DESCRIPTION Allows easy conversion of OperatingSystem, Operating System Version to proper Windows 10 naming based on WMI or AD .PARAMETER OperatingSystem Operating System as returned by Active Directory .PARAMETER OperatingSystemVersion Operating System Version as returned by Active Directory .EXAMPLE $Computers = Get-ADComputer -Filter * -Properties OperatingSystem, OperatingSystemVersion | ForEach-Object { $OPS = ConvertTo-OperatingSystem -OperatingSystem $_.OperatingSystem -OperatingSystemVersion $_.OperatingSystemVersion Add-Member -MemberType NoteProperty -Name 'OperatingSystemTranslated' -Value $OPS -InputObject $_ -Force $_ } $Computers | Select-Object DNS*, Name, SamAccountName, Enabled, OperatingSystem*, DistinguishedName | Format-Table .EXAMPLE $Registry = Get-PSRegistry -ComputerName 'AD1' -RegistryPath 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' ConvertTo-OperatingSystem -OperatingSystem $Registry.ProductName -OperatingSystemVersion $Registry.CurrentBuildNumber .NOTES General notes #> [CmdletBinding()] param( [string] $OperatingSystem, [string] $OperatingSystemVersion ) if ($OperatingSystem -like 'Windows 10*' -or $OperatingSystem -like 'Windows 11*') { $Systems = @{ '10.0 (22621)' = 'Windows 11 22H2' '10.0 (22000)' = 'Windows 11 21H2' '10.0 (19045)' = 'Windows 10 22H2' '10.0 (19044)' = 'Windows 10 21H2' '10.0 (19043)' = 'Windows 10 21H1' '10.0 (19042)' = 'Windows 10 20H2' '10.0 (19041)' = 'Windows 10 2004' '10.0 (18898)' = 'Windows 10 Insider Preview' '10.0 (18363)' = "Windows 10 1909" '10.0 (18362)' = "Windows 10 1903" '10.0 (17763)' = "Windows 10 1809" '10.0 (17134)' = "Windows 10 1803" '10.0 (16299)' = "Windows 10 1709" '10.0 (15063)' = "Windows 10 1703" '10.0 (14393)' = "Windows 10 1607" '10.0 (10586)' = "Windows 10 1511" '10.0 (10240)' = "Windows 10 1507" '10.0.22621' = 'Windows 11 22H2' '10.0.22000' = 'Windows 11 21H2' '10.0.19045' = 'Windows 10 22H2' '10.0.19044' = 'Windows 10 21H2' '10.0.19043' = 'Windows 10 21H1' '10.0.19042' = 'Windows 10 20H2' '10.0.19041' = 'Windows 10 2004' '10.0.18898' = 'Windows 10 Insider Preview' '10.0.18363' = "Windows 10 1909" '10.0.18362' = "Windows 10 1903" '10.0.17763' = "Windows 10 1809" '10.0.17134' = "Windows 10 1803" '10.0.16299' = "Windows 10 1709" '10.0.15063' = "Windows 10 1703" '10.0.14393' = "Windows 10 1607" '10.0.10586' = "Windows 10 1511" '10.0.10240' = "Windows 10 1507" '22621' = 'Windows 11 22H2' '22000' = 'Windows 11 21H2' '19045' = 'Windows 10 22H2' '19044' = 'Windows 10 21H2' '19043' = 'Windows 10 21H1' '19042' = 'Windows 10 20H2' '19041' = 'Windows 10 2004' '18898' = 'Windows 10 Insider Preview' '18363' = "Windows 10 1909" '18362' = "Windows 10 1903" '17763' = "Windows 10 1809" '17134' = "Windows 10 1803" '16299' = "Windows 10 1709" '15063' = "Windows 10 1703" '14393' = "Windows 10 1607" '10586' = "Windows 10 1511" '10240' = "Windows 10 1507" } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } elseif ($OperatingSystem -like 'Windows Server*') { $Systems = @{ '10.0 (20348)' = 'Windows Server 2022' '10.0 (19042)' = 'Windows Server 2019 20H2' '10.0 (19041)' = 'Windows Server 2019 2004' '10.0 (18363)' = 'Windows Server 2019 1909' '10.0 (18362)' = "Windows Server 2019 1903" '10.0 (17763)' = "Windows Server 2019 1809" '10.0 (17134)' = "Windows Server 2016 1803" '10.0 (14393)' = "Windows Server 2016 1607" '6.3 (9600)' = 'Windows Server 2012 R2' '6.1 (7601)' = 'Windows Server 2008 R2' '5.2 (3790)' = 'Windows Server 2003' '10.0.20348' = 'Windows Server 2022' '10.0.19042' = 'Windows Server 2019 20H2' '10.0.19041' = 'Windows Server 2019 2004' '10.0.18363' = 'Windows Server 2019 1909' '10.0.18362' = "Windows Server 2019 1903" '10.0.17763' = "Windows Server 2019 1809" '10.0.17134' = "Windows Server 2016 1803" '10.0.14393' = "Windows Server 2016 1607" '6.3.9600' = 'Windows Server 2012 R2' '6.1.7601' = 'Windows Server 2008 R2' '5.2.3790' = 'Windows Server 2003' '20348' = 'Windows Server 2022' '19042' = 'Windows Server 2019 20H2' '19041' = 'Windows Server 2019 2004' '18363' = 'Windows Server 2019 1909' '18362' = "Windows Server 2019 1903" '17763' = "Windows Server 2019 1809" '17134' = "Windows Server 2016 1803" '14393' = "Windows Server 2016 1607" '9600' = 'Windows Server 2012 R2' '7601' = 'Windows Server 2008 R2' '3790' = 'Windows Server 2003' } $System = $Systems[$OperatingSystemVersion] if (-not $System) { $System = $OperatingSystemVersion } } else { $System = $OperatingSystem } if ($System) { $System } else { 'Unknown' } } function ConvertTo-OrderedDictionary { <# .SYNOPSIS Converts a hashtable into an ordered dictionary. .DESCRIPTION This function takes a hashtable as input and converts it into an ordered dictionary. The ordered dictionary maintains the order of elements as they were added to the hashtable. .PARAMETER HashTable Specifies the hashtable to be converted into an ordered dictionary. .EXAMPLE $HashTable = @{ "Key3" = "Value3" "Key1" = "Value1" "Key2" = "Value2" } ConvertTo-OrderedDictionary -HashTable $HashTable # Outputs an ordered dictionary where the keys are in the order they were added to the hashtable. #> [CmdletBinding()] Param ( [parameter(Mandatory = $true, ValueFromPipeline = $true)] $HashTable ) $OrderedDictionary = [ordered]@{ } if ($HashTable -is [System.Collections.IDictionary]) { $Keys = $HashTable.Keys | Sort-Object foreach ($_ in $Keys) { $OrderedDictionary.Add($_, $HashTable[$_]) } } elseif ($HashTable -is [System.Collections.ICollection]) { for ($i = 0; $i -lt $HashTable.count; $i++) { $OrderedDictionary.Add($i, $HashTable[$i]) } } else { Write-Error "ConvertTo-OrderedDictionary - Wrong input type." } return $OrderedDictionary } function ConvertTo-PrettyObject { <# .SYNOPSIS Command to help with converting standard objects that could be nested objects into single level PSCustomObject .DESCRIPTION Command to help with converting standard objects that could be nested objects into single level PSCustomObject This is a help command for PSWriteHTML module and probably PSWriteOffice module to create tables from objects and make sure those tables are not nested and can be easily converted to HTML or Office tables without having to manually flatten them .PARAMETER Object Specifies the objects to convert to pretty format. Enter a variable that contains the objects, or type a command or expression that gets the objects. You can also pipe an object to ConvertTo-JsonLiteral .PARAMETER DateTimeFormat Changes DateTime string format. Default "yyyy-MM-dd HH:mm:ss" .PARAMETER NumberAsString Provides an alternative serialization option that converts all numbers to their string representation. .PARAMETER BoolAsString Provides an alternative serialization option that converts all bool to their string representation. .PARAMETER PropertyName Uses PropertyNames provided by user (only works with Force) .PARAMETER NewLineFormat Provides a way to configure how new lines are converted for property names .PARAMETER NewLineFormatProperty Provides a way to configure how new lines are converted for values .PARAMETER PropertyName Allows passing property names to be used for custom objects (hashtables and alike are unaffected) .PARAMETER ArrayJoin Forces any array to be a string regardless of depth level .PARAMETER ArrayJoinString Uses defined string or char for array join. By default it uses comma with a space when used. .PARAMETER Force Forces using property names from first object or given thru PropertyName parameter .EXAMPLE $Test1 = [PSCustomObject] @{ Number = 1 Number2 = 2.2 Bool = $false Array = @( 'C:\Users\1Password.exe' "C:\Users\Ooops.exe" "\\EvoWin\c$\Users\przemyslaw klys\AppData\Local\1password\This is other\7\1Password.exe" "\\EvoWin\c$\Users\przemyslaw.klys\AppData\Local\1password\This is other\7\1Password.exe" ) EmptyArray = @() EmptyList = [System.Collections.Generic.List[string]]::new() HashTable = @{ NumberAgain = 2 OrderedDictionary = [ordered] @{ String = 'test' HashTable = @{ StringAgain = "oops" } } Array = @( 'C:\Users\1Password.exe' "C:\Users\Ooops.exe" "\\EvoWin\c$\Users\przemyslaw klys\AppData\Local\1password\This is other\7\1Password.exe" "\\EvoWin\c$\Users\przemyslaw.klys\AppData\Local\1password\This is other\7\1Password.exe" ) } DateTime = Get-Date } $Test1 | ConvertTo-PrettyObject -ArrayJoinString "," -ArrayJoin | ConvertTo-Json | ConvertFrom-Json .NOTES General notes #> [CmdletBinding()] param( [alias('InputObject')][Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName, Position = 0, Mandatory)][Array] $Object, [int] $Depth, [switch] $AsArray, [string] $DateTimeFormat = "yyyy-MM-dd HH:mm:ss", [switch] $NumberAsString, [switch] $BoolAsString, [System.Collections.IDictionary] $NewLineFormat = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $NewLineFormatProperty = @{ NewLineCarriage = '\r\n' NewLine = "\n" Carriage = "\r" }, [System.Collections.IDictionary] $AdvancedReplace, [string] $ArrayJoinString, [switch] $ArrayJoin, [string[]]$PropertyName, [switch] $Force ) Begin { filter IsNumeric() { return $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } filter IsOfType() { return $_ -is [bool] -or $_ -is [char] -or $_ -is [datetime] -or $_ -is [string] ` -or $_ -is [timespan] -or $_ -is [URI] ` -or $_ -is [byte] -or $_ -is [int16] -or $_ -is [int32] -or $_ -is [int64] ` -or $_ -is [sbyte] -or $_ -is [uint16] -or $_ -is [uint32] -or $_ -is [uint64] ` -or $_ -is [float] -or $_ -is [double] -or $_ -is [decimal] } } Process { for ($a = 0; $a -lt $Object.Count; $a++) { $NewObject = [ordered] @{} if ($Object[$a] -is [System.Collections.IDictionary]) { for ($i = 0; $i -lt ($Object[$a].Keys).Count; $i++) { $Property = ([string[]]$Object[$a].Keys)[$i] $DisplayProperty = $Property.Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $Value = $Object[$a].$Property if ($null -eq $Value) { $NewObject[$DisplayProperty] = "" } elseif ($Value -is [string]) { foreach ($Key in $AdvancedReplace.Keys) { $Value = $Value.Replace($Key, $AdvancedReplace[$Key]) } $NewObject[$DisplayProperty] = $Value.Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) } elseif ($Value -is [DateTime]) { $NewObject[$DisplayProperty] = $Object[$a].$Property.ToString($DateTimeFormat) } elseif ($Value -is [bool]) { if ($BoolAsString) { $NewObject[$DisplayProperty] = "$Value" } else { $NewObject[$DisplayProperty] = $Value } } elseif ($Value -is [System.Collections.IDictionary]) { $NewObject[$DisplayProperty] = "$Value" } elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) { if ($ArrayJoin) { $Value = $Value -join $ArrayJoinString $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } else { $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } } elseif ($Value -is [System.Enum]) { $NewObject[$DisplayProperty] = ($Value).ToString() } elseif (($Value | IsNumeric) -eq $true) { $Value = $($Value).ToString().Replace(',', '.') if ($NumberAsString) { $NewObject[$DisplayProperty] = "$Value" } else { $NewObject[$DisplayProperty] = $Value } } elseif ($Value -is [PSObject]) { $NewObject[$DisplayProperty] = "$Value" } else { $Value = $Value.ToString().Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } } [PSCustomObject] $NewObject } elseif ($Object[$a] | IsOfType) { $Object[$a] } else { if ($Force -and -not $PropertyName) { $PropertyName = $Object[0].PSObject.Properties.Name } elseif ($Force -and $PropertyName) { } else { $PropertyName = $Object[$a].PSObject.Properties.Name } foreach ($Property in $PropertyName) { $DisplayProperty = $Property.Replace([System.Environment]::NewLine, $NewLineFormatProperty.NewLineCarriage).Replace("`n", $NewLineFormatProperty.NewLine).Replace("`r", $NewLineFormatProperty.Carriage) $Value = $Object[$a].$Property if ($null -eq $Value) { $NewObject[$DisplayProperty] = "" } elseif ($Value -is [string]) { foreach ($Key in $AdvancedReplace.Keys) { $Value = $Value.Replace($Key, $AdvancedReplace[$Key]) } $NewObject[$DisplayProperty] = $Value.Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) } elseif ($Value -is [DateTime]) { $NewObject[$DisplayProperty] = $Object[$a].$Property.ToString($DateTimeFormat) } elseif ($Value -is [bool]) { if ($BoolAsString) { $NewObject[$DisplayProperty] = "$Value" } else { $NewObject[$DisplayProperty] = $Value } } elseif ($Value -is [System.Collections.IDictionary]) { $NewObject[$DisplayProperty] = "$Value" } elseif ($Value -is [System.Collections.IList] -or $Value -is [System.Collections.ReadOnlyCollectionBase]) { if ($ArrayJoin) { $Value = $Value -join $ArrayJoinString $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } else { $Value = "$Value".Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } } elseif ($Value -is [System.Enum]) { $NewObject[$DisplayProperty] = ($Value).ToString() } elseif (($Value | IsNumeric) -eq $true) { if ($NumberAsString) { $NewObject[$DisplayProperty] = "$Value" } else { $NewObject[$DisplayProperty] = $Value } } elseif ($Value -is [PSObject]) { $NewObject[$DisplayProperty] = "$Value" } else { $Value = $Value.ToString().Replace([System.Environment]::NewLine, $NewLineFormat.NewLineCarriage).Replace("`n", $NewLineFormat.NewLine).Replace("`r", $NewLineFormat.Carriage) $NewObject[$DisplayProperty] = "$Value" } } [PSCustomObject] $NewObject } } } } function ConvertTo-SID { <# .SYNOPSIS Converts a given identity to a Security Identifier (SID). .DESCRIPTION This function takes one or more identity strings and converts them to their corresponding Security Identifiers (SIDs). It caches the results for faster lookup. .PARAMETER Identity Specifies the identity strings to be converted to SIDs. .EXAMPLE ConvertTo-SID -Identity 'Administrator' Converts the 'Administrator' identity to its corresponding SID. .EXAMPLE ConvertTo-SID -Identity 'Guest', 'User1' Converts the 'Guest' and 'User1' identities to their corresponding SIDs. #> [cmdletBinding()] param( [string[]] $Identity ) Begin { if (-not $Script:GlobalCacheSidConvert) { $Script:GlobalCacheSidConvert = @{ } } } Process { foreach ($Ident in $Identity) { if ($Script:GlobalCacheSidConvert[$Ident]) { $Script:GlobalCacheSidConvert[$Ident] } else { try { $Script:GlobalCacheSidConvert[$Ident] = [PSCustomObject] @{ Name = $Ident Sid = ([System.Security.Principal.NTAccount] $Ident).Translate([System.Security.Principal.SecurityIdentifier]).Value Error = '' } } catch { [PSCustomObject] @{ Name = $Ident Sid = '' Error = $_.Exception.Message } } $Script:GlobalCacheSidConvert[$Ident] } } } End { } } function Find-DatesCurrentDayMinusDayX ($days) { <# .SYNOPSIS Finds the date range for the current day minus a specified number of days. .DESCRIPTION This function calculates the start and end dates for the current day minus a specified number of days. .PARAMETER days Specifies the number of days to subtract from the current day. .EXAMPLE Find-DatesCurrentDayMinusDayX -days 1 Returns the date range for yesterday. .EXAMPLE Find-DatesCurrentDayMinusDayX -days 7 Returns the date range for a week ago. #> $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays( - $Days) $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddDays( - $Days).AddMilliseconds(-1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesCurrentDayMinuxDaysX ($days) { <# .SYNOPSIS Finds the date range for the current day minus a specified number of days. .DESCRIPTION This function calculates the start and end dates for the current day minus a specified number of days. .PARAMETER days Specifies the number of days to subtract from the current day. .EXAMPLE Find-DatesCurrentDayMinuxDaysX -days 1 Returns the date range for yesterday. .EXAMPLE Find-DatesCurrentDayMinuxDaysX -days 7 Returns the date range for a week ago. #> $DateTodayStart = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays( - $Days) $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(1).AddMilliseconds(-1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesCurrentHour () { <# .SYNOPSIS Finds the start and end dates for the current hour. .DESCRIPTION This function calculates the start and end dates for the current hour. .EXAMPLE PS C:\> Find-DatesCurrentHour DateFrom DateTo -------- ------ 10/20/2021 12:00:00 AM 10/20/2021 1:00:00 AM #> $DateTodayStart = (Get-Date -Minute 0 -Second 0 -Millisecond 0) $DateTodayEnd = $DateTodayStart.AddHours(1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesDayPrevious () { <# .SYNOPSIS Finds the date parameters for the previous day. .DESCRIPTION This function calculates the date parameters for the previous day based on the current date. .EXAMPLE Find-DatesDayPrevious Returns the date parameters for the previous day. #> $DateToday = (Get-Date).Date $DateYesterday = $DateToday.AddDays(-1) $DateParameters = @{ DateFrom = $DateYesterday DateTo = $dateToday } return $DateParameters } function Find-DatesDayToday () { <# .SYNOPSIS Finds the start and end dates of the current day. .DESCRIPTION This function calculates the start and end dates of the current day based on the current date. #> $DateToday = (Get-Date).Date $DateTodayEnd = $DateToday.AddDays(1).AddSeconds(-1) $DateParameters = @{ DateFrom = $DateToday DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesMonthCurrent () { <# .SYNOPSIS Finds the start and end dates of the current month. .DESCRIPTION This function calculates the start and end dates of the current month based on the current date. .EXAMPLE Find-DatesMonthCurrent Returns the start and end dates of the current month. #> $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthLastDay = Get-Date $DateMonthFirstDay.AddMonths(1).AddSeconds(-1) $DateParameters = @{ DateFrom = $DateMonthFirstDay DateTo = $DateMonthLastDay } return $DateParameters } function Find-DatesMonthPast ([bool] $Force) { <# .SYNOPSIS Finds the dates for the previous month based on the current date. .DESCRIPTION This function calculates the date range for the previous month based on the current date. It returns the start and end dates of the previous month. .PARAMETER Force If set to $true, the function will always return the date range for the previous month, regardless of the current date. .EXAMPLE Find-DatesMonthPast -Force $false Returns $null if the current date is not the first day of the month. .EXAMPLE Find-DatesMonthPast -Force $true Returns the date range for the previous month even if the current date is not the first day of the month. #> $DateToday = (Get-Date).Date $DateMonthFirstDay = (Get-Date -Day 1).Date $DateMonthPreviousFirstDay = $DateMonthFirstDay.AddMonths(-1) if ($Force -eq $true -or $DateToday -eq $DateMonthFirstDay) { $DateParameters = @{ DateFrom = $DateMonthPreviousFirstDay DateTo = $DateMonthFirstDay } return $DateParameters } else { return $null } } function Find-DatesPastHour () { <# .SYNOPSIS Finds the date range for the past hour. .DESCRIPTION This function calculates the date range for the past hour, starting from the beginning of the previous hour up to the current hour. .EXAMPLE Find-DatesPastHour Returns a hashtable with DateFrom and DateTo keys representing the date range for the past hour. #> $DateTodayEnd = Get-Date -Minute 0 -Second 0 -Millisecond 0 $DateTodayStart = $DateTodayEnd.AddHours(-1) $DateParameters = @{ DateFrom = $DateTodayStart DateTo = $DateTodayEnd } return $DateParameters } function Find-DatesPastWeek($DayName) { <# .SYNOPSIS Finds the date range for the past week based on the specified day. .DESCRIPTION This function calculates the date range for the past week based on the specified day of the week. .PARAMETER DayName The day of the week to use as a reference for finding the past week's date range. .EXAMPLE Find-DatesPastWeek -DayName "Monday" Returns the date range for the past week starting from the previous Monday. .EXAMPLE Find-DatesPastWeek -DayName "Friday" Returns the date range for the past week starting from the previous Friday. #> $DateTodayStart = Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0 if ($DateTodayStart.DayOfWeek -ne $DayName) { return $null } $DateTodayEnd = (Get-Date -Hour 0 -Minute 0 -Second 0 -Millisecond 0).AddDays(-7) $DateParameters = @{ DateFrom = $DateTodayEnd DateTo = $DateTodayStart } return $DateParameters } function Find-DatesQuarterCurrent ([bool] $Force) { <# .SYNOPSIS Finds the start and end dates of the current quarter. .DESCRIPTION This function calculates the start and end dates of the current quarter based on the current date. .PARAMETER Force If set to $true, forces the function to recalculate the dates even if they have been previously calculated. .EXAMPLE Find-DatesQuarterCurrent -Force $false Returns the start and end dates of the current quarter without recalculating if already calculated. .EXAMPLE Find-DatesQuarterCurrent -Force $true Forces the function to recalculate and returns the start and end dates of the current quarter. #> $Today = (Get-Date) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) $DateParameters = @{ DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } function Find-DatesQuarterLast ([bool] $Force) { <# .SYNOPSIS Finds the start and end dates of the last quarter. .DESCRIPTION This function calculates the start and end dates of the last quarter based on the current date or a specified date. .PARAMETER Force If set to $true, forces the function to return the last quarter dates even if they were previously retrieved. .EXAMPLE Find-DatesQuarterLast -Force Returns the start and end dates of the last quarter regardless of previous retrieval. .EXAMPLE Find-DatesQuarterLast -Force $false Returns $null if the last quarter dates were already retrieved. #> #https://blogs.technet.microsoft.com/dsheehan/2017/09/21/use-powershell-to-determine-the-first-day-of-the-current-calendar-quarter/ $Today = (Get-Date).AddDays(-90) $Yesterday = ((Get-Date).AddDays(-1)) $Quarter = [Math]::Ceiling($Today.Month / 3) $LastDay = [DateTime]::DaysInMonth([Int]$Today.Year.ToString(), [Int]($Quarter * 3)) $StartDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3 - 2) -Day 1).Date $EndDate = (Get-Date -Year $Today.Year -Month ($Quarter * 3) -Day $LastDay).Date.AddDays(1).AddTicks(-1) if ($Force -eq $true -or $Yesterday.Date -eq $EndDate.Date) { $DateParameters = @{ DateFrom = $StartDate DateTo = $EndDate } return $DateParameters } else { return $null } } function Set-DnsServerIpAddress { <# .SYNOPSIS Sets the DNS server IP addresses on a specified computer for a given network interface. .DESCRIPTION This function allows you to set the DNS server IP addresses on a specified computer for a given network interface. It checks if the computer is online before attempting to set the DNS server addresses. .PARAMETER ComputerName Specifies the name of the computer where the DNS server IP addresses will be set. .PARAMETER NicName Specifies the name of the network interface (NIC) where the DNS server IP addresses will be set. Supports wildcard characters. .PARAMETER IpAddresses Specifies one or more IP addresses of the DNS servers to be set on the specified network interface. .EXAMPLE Set-DnsServerIpAddress -ComputerName "Server01" -NicName "Ethernet*" -IpAddresses '8.8.8.8','8.8.4.4','8.8.8.1' Sets the DNS server IP addresses '8.8.8.8', '8.8.4.4', and '8.8.8.1' on the network interface starting with "Ethernet" on the computer "Server01". .NOTES This function may require further enhancements for specific use cases. #> [CmdletBinding()] param( [string] $ComputerName, [string] $NicName, [string] $IpAddresses ) if (Test-Connection -ComputerName $ComputerName -Count 2 -Quiet) { Invoke-Command -ComputerName $ComputerName -ScriptBlock { param ($ComputerName, $NicName, $IpAddresses) Write-Host "Setting on $ComputerName on interface $NicName a new set of DNS Servers $IpAddresses" Set-DnsClientServerAddress -InterfaceAlias $NicName -ServerAddresses $IpAddresses } -ArgumentList $ComputerName, $NicName, $IpAddresses } else { Write-Warning "Set-DnsServerIpAddress - Can't access $ComputerName. Computer is not online." } } function Get-HTML { <# .SYNOPSIS Splits the input text by carriage return and outputs each line. .DESCRIPTION This function takes a string input and splits it by carriage return (`r) to output each line separately. .PARAMETER text The input text to be split and displayed line by line. .EXAMPLE Get-HTML -text "Line 1`rLine 2`rLine 3" This example splits the input text by carriage return and outputs each line separately. #> [CmdletBinding()] param ( [string] $text ) $text = $text.Split("`r") foreach ($t in $text) { Write-Host $t } } function Send-Email { <# .SYNOPSIS Sends an email with specified parameters. .DESCRIPTION This function sends an email using the provided parameters. It supports sending emails with attachments and inline attachments. .PARAMETER Email Specifies the email parameters including sender, recipients, server settings, and encoding. .PARAMETER Body Specifies the body of the email. .PARAMETER Attachment Specifies an array of file paths to be attached to the email. .PARAMETER InlineAttachments Specifies a dictionary of inline attachments to be included in the email. .PARAMETER Subject Specifies the subject of the email. .PARAMETER To Specifies an array of email addresses to send the email to. .PARAMETER Logger Specifies a custom object for logging purposes. .EXAMPLE Send-Email -Email $EmailParams -Body "Hello, this is a test email" -Attachment "C:\Files\attachment.txt" -Subject "Test Email" -To "recipient@example.com" -Logger $Logger .EXAMPLE $EmailParams = @{ From = "sender@example.com" To = "recipient@example.com" Subject = "Test Email" Body = "Hello, this is a test email" Server = "smtp.example.com" Port = 587 Password = "password" Encoding = "UTF8" } Send-Email -Email $EmailParams -Attachment "C:\Files\attachment.txt" -To "recipient@example.com" -Logger $Logger #> [CmdletBinding(SupportsShouldProcess = $true)] param ( [alias('EmailParameters')][System.Collections.IDictionary] $Email, [string] $Body, [string[]] $Attachment, [System.Collections.IDictionary] $InlineAttachments, [string] $Subject, [string[]] $To, [PSCustomObject] $Logger ) try { if ($Email.EmailTo) { $EmailParameters = $Email.Clone() $EmailParameters.EmailEncoding = $EmailParameters.EmailEncoding -replace "-", '' $EmailParameters.EmailEncodingSubject = $EmailParameters.EmailEncodingSubject -replace "-", '' $EmailParameters.EmailEncodingBody = $EmailParameters.EmailEncodingSubject -replace "-", '' $EmailParameters.EmailEncodingAlternateView = $EmailParameters.EmailEncodingAlternateView -replace "-", '' } else { $EmailParameters = @{ EmailFrom = $Email.From EmailTo = $Email.To EmailCC = $Email.CC EmailBCC = $Email.BCC EmailReplyTo = $Email.ReplyTo EmailServer = $Email.Server EmailServerPassword = $Email.Password EmailServerPasswordAsSecure = $Email.PasswordAsSecure EmailServerPasswordFromFile = $Email.PasswordFromFile EmailServerPort = $Email.Port EmailServerLogin = $Email.Login EmailServerEnableSSL = $Email.EnableSsl EmailEncoding = $Email.Encoding -replace "-", '' EmailEncodingSubject = $Email.EncodingSubject -replace "-", '' EmailEncodingBody = $Email.EncodingBody -replace "-", '' EmailEncodingAlternateView = $Email.EncodingAlternateView -replace "-", '' EmailSubject = $Email.Subject EmailPriority = $Email.Priority EmailDeliveryNotifications = $Email.DeliveryNotifications EmailUseDefaultCredentials = $Email.UseDefaultCredentials } } } catch { return @{ Status = $False Error = $($_.Exception.Message) SentTo = '' } } $SmtpClient = [System.Net.Mail.SmtpClient]::new() if ($EmailParameters.EmailServer) { $SmtpClient.Host = $EmailParameters.EmailServer } else { return @{ Status = $False Error = "Email Server Host is not set." SentTo = '' } } if ($EmailParameters.EmailServerPort) { $SmtpClient.Port = $EmailParameters.EmailServerPort } else { return @{ Status = $False Error = "Email Server Port is not set." SentTo = '' } } if ($EmailParameters.EmailServerLogin) { $Credentials = Request-Credentials -UserName $EmailParameters.EmailServerLogin ` -Password $EmailParameters.EmailServerPassword ` -AsSecure:$EmailParameters.EmailServerPasswordAsSecure ` -FromFile:$EmailParameters.EmailServerPasswordFromFile ` -NetworkCredentials $SmtpClient.Credentials = $Credentials } if ($EmailParameters.EmailServerEnableSSL) { $SmtpClient.EnableSsl = $EmailParameters.EmailServerEnableSSL } $MailMessage = [System.Net.Mail.MailMessage]::new() $MailMessage.From = $EmailParameters.EmailFrom if ($To) { foreach ($T in $To) { $MailMessage.To.add($($T)) } } else { if ($EmailParameters.Emailto) { foreach ($To in $EmailParameters.Emailto) { $MailMessage.To.add($($To)) } } } if ($EmailParameters.EmailCC) { foreach ($CC in $EmailParameters.EmailCC) { $MailMessage.CC.add($($CC)) } } if ($EmailParameters.EmailBCC) { foreach ($BCC in $EmailParameters.EmailBCC) { $MailMessage.BCC.add($($BCC)) } } if ($EmailParameters.EmailReplyTo) { $MailMessage.ReplyTo = $EmailParameters.EmailReplyTo } $MailMessage.IsBodyHtml = $true if ($Subject -eq '') { $MailMessage.Subject = $EmailParameters.EmailSubject } else { $MailMessage.Subject = $Subject } $MailMessage.Priority = [System.Net.Mail.MailPriority]::$($EmailParameters.EmailPriority) if ($EmailParameters.EmailEncodingSubject) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingSubject) } elseif ($EmailParameters.EmailEncoding) { $MailMessage.SubjectEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) } if ($EmailParameters.EmailEncodingBody) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncodingBody) } elseif ($EmailParameters.EmailEncoding) { $MailMessage.BodyEncoding = [System.Text.Encoding]::$($EmailParameters.EmailEncoding) } if ($EmailParameters.EmailUseDefaultCredentials) { $SmtpClient.UseDefaultCredentials = $EmailParameters.EmailUseDefaultCredentials } if ($EmailParameters.EmailDeliveryNotifications) { $MailMessage.DeliveryNotificationOptions = $EmailParameters.EmailDeliveryNotifications } if ($PSBoundParameters.ContainsKey('InlineAttachments')) { if ($EmailParameters.EmailEncodingAlternateView) { $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::$($EmailParameters.EmailEncodingAlternateView) , 'text/html' ) } else { $BodyPart = [Net.Mail.AlternateView]::CreateAlternateViewFromString($Body, [System.Text.Encoding]::UTF8, 'text/html' ) } $MailMessage.AlternateViews.Add($BodyPart) foreach ($Entry in $InlineAttachments.GetEnumerator()) { try { $FilePath = $Entry.Value Write-Verbose $FilePath if ($Entry.Value.StartsWith('http', [System.StringComparison]::CurrentCultureIgnoreCase)) { $FileName = $Entry.Value.Substring($Entry.Value.LastIndexOf("/") + 1) $FilePath = Join-Path $env:temp $FileName Invoke-WebRequest -Uri $Entry.Value -OutFile $FilePath } $ContentType = Get-MimeType -FileName $FilePath $InAttachment = [Net.Mail.LinkedResource]::new($FilePath, $ContentType ) $InAttachment.ContentId = $Entry.Key $BodyPart.LinkedResources.Add( $InAttachment ) } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " Write-Error "Error inlining attachments: $ErrorMessage" } } } else { $MailMessage.Body = $Body } if ($PSBoundParameters.ContainsKey('Attachment')) { foreach ($Attach in $Attachment) { if (Test-Path -LiteralPath $Attach) { try { $File = [Net.Mail.Attachment]::new($Attach) $MailMessage.Attachments.Add($File) } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " if ($Logger) { $Logger.AddErrorRecord("Error attaching file $Attach`: $ErrorMessage") } else { Write-Error "Error attaching file $Attach`: $ErrorMessage" } } } } } try { $MailSentTo = "$($MailMessage.To) $($MailMessage.CC) $($MailMessage.BCC)".Trim() if ($pscmdlet.ShouldProcess("$MailSentTo", "Send-Email")) { $SmtpClient.Send($MailMessage) $MailMessage.Dispose(); return [PSCustomObject] @{ Status = $True Error = "" SentTo = $MailSentTo } } else { return [PSCustomObject] @{ Status = $False Error = 'Email not sent (WhatIf)' SentTo = $MailSentTo } } } catch { $MailMessage.Dispose(); return [PSCustomObject] @{ Status = $False Error = $($_.Exception.Message) SentTo = "" } } } function Set-EmailBody { <# .SYNOPSIS Sets the email body content with a welcome message and table data. .DESCRIPTION This function sets the email body content by combining a welcome message and table data into a single HTML string. If there is no data in the table, a default message is displayed. .PARAMETER TableData The data to be included in the email body table. .PARAMETER TableMessageWelcome The welcome message to be displayed at the beginning of the email body. .PARAMETER TableMessageNoData The message to be displayed when there is no data in the table. .EXAMPLE $tableData = @("Name", "Age", "Location"), @("Alice", "30", "New York"), @("Bob", "25", "Los Angeles") $welcomeMessage = "Welcome to our platform!" Set-EmailBody -TableData $tableData -TableMessageWelcome $welcomeMessage This example sets the email body with a welcome message "Welcome to our platform!" and table data. If there is no data in the table, the default message "No changes happened during that period." is displayed. #> [CmdletBinding()] param( [Object] $TableData, [alias('TableWelcomeMessage')][string] $TableMessageWelcome, [string] $TableMessageNoData = 'No changes happened during that period.' ) $Body = "<p><i><u>$TableMessageWelcome</u></i></p>" if ($($TableData | Measure-Object).Count -gt 0) { $Body += $TableData | ConvertTo-Html -Fragment | Out-String } else { $Body += "<p><i>$TableMessageNoData</i></p>" } return $body } function Set-EmailBodyPreparedTable ($TableData, $TableWelcomeMessage) { <# .SYNOPSIS Prepares the email body with a welcome message and table data. .DESCRIPTION This function prepares the email body by combining a welcome message and table data into a single HTML string. .PARAMETER TableData The data to be included in the email body table. .PARAMETER TableWelcomeMessage The welcome message to be displayed at the beginning of the email body. .EXAMPLE $tableData = "<table><tr><td>John</td><td>Doe</td></tr></table>" $welcomeMessage = "Welcome to our platform!" Set-EmailBodyPreparedTable -TableData $tableData -TableWelcomeMessage $welcomeMessage This example prepares the email body with a welcome message "Welcome to our platform!" and table data "<table><tr><td>John</td><td>Doe</td></tr></table>". #> $body = "<p><i><u>$TableWelcomeMessage</u></i></p>" $body += $TableData return $body } function Set-EmailBodyReplacement { <# .SYNOPSIS Replaces specified text in the email body based on the provided replacement table and type. .DESCRIPTION This function replaces text in the email body with specified formatting based on the replacement table and type provided. .PARAMETER Body The original email body text. .PARAMETER ReplacementTable A hashtable containing the text to be replaced as keys and the replacement values as values. .PARAMETER Type The type of replacement to be performed. Valid values are 'Colors' and 'Bold'. .EXAMPLE Set-EmailBodyReplacement -Body "This is a test email." -ReplacementTable @{ 'test' = 'green' } -Type Colors This example replaces the text 'test' in the email body with green color. .EXAMPLE Set-EmailBodyReplacement -Body "This is a test email." -ReplacementTable @{ 'test' = $true } -Type Bold This example makes the text 'test' bold in the email body. #> [CmdletBinding()] param( [string] $Body, [hashtable] $ReplacementTable, [ValidateSet('Colors', 'Bold')][string] $Type ) switch ($Type) { 'Colors' { foreach ($Field in $ReplacementTable.Keys) { $Value = $ReplacementTable.$Field $Body = $Body -replace $Field, "<font color=`"$Value`">$Field</font>" } } 'Bold' { foreach ($Field in $ReplacementTable.Keys) { $Value = $ReplacementTable.$Field if ($Value -eq $true) { $Body = $Body -replace $Field, "<b>$Field</b>" } } } } return $Body } function Set-EmailBodyReplacementTable { <# .SYNOPSIS Replaces a placeholder in the email body with an HTML table. .DESCRIPTION This function replaces a specified placeholder in the email body with an HTML table generated from the provided table data. .PARAMETER Body The original email body containing the placeholder to be replaced. .PARAMETER TableName The placeholder text to be replaced with the HTML table. .PARAMETER TableData An array of data to be converted into an HTML table. .EXAMPLE $body = "Hello, <<TablePlaceholder>>!" $tableName = "TablePlaceholder" $tableData = @("Name", "Age", "Location"), @("Alice", "30", "New York"), @("Bob", "25", "Los Angeles") Set-EmailBodyReplacementTable -Body $body -TableName $tableName -TableData $tableData This example replaces the placeholder "<<TablePlaceholder>>" in the email body with an HTML table containing the provided data. #> [CmdletBinding()] [alias('Set-EmailBodyTableReplacement')] param ( [string] $Body, [string] $TableName, [Array] $TableData ) $TableData = $TableData | ConvertTo-Html -Fragment | Out-String $Body = $Body -replace "<<$TableName>>", $TableData return $Body } function Set-EmailFormatting { <# .SYNOPSIS Sets the formatting for an email template. .DESCRIPTION This function sets the formatting for an email template by applying specified styles and parameters. .PARAMETER Template The email template to be formatted. .PARAMETER FormattingParameters A dictionary containing the styles to be applied to the template. .PARAMETER ConfigurationParameters A dictionary containing configuration parameters for formatting. .PARAMETER Logger An object used for logging information. .PARAMETER SkipNewLines Switch to skip adding new lines to the template. .PARAMETER AddAfterOpening An array of strings to add after the opening of the template. .PARAMETER AddBeforeClosing An array of strings to add before the closing of the template. .PARAMETER Image An optional image to be included in the template. .EXAMPLE Set-EmailFormatting -Template "Hello, <<Name>>!" -FormattingParameters @{ Styles = @{ Name = "b" } } -ConfigurationParameters @{ DisplayConsole = $true } -Logger $logger -Image "logo.png" Description: Sets the formatting for an email template with a bold style applied to the name placeholder and includes a logo image. #> [CmdletBinding()] param ( $Template, [System.Collections.IDictionary] $FormattingParameters, [System.Collections.IDictionary] $ConfigurationParameters, [PSCustomObject] $Logger, [switch] $SkipNewLines, [string[]] $AddAfterOpening, [string[]] $AddBeforeClosing, [string] $Image ) if ($ConfigurationParameters) { $WriteParameters = $ConfigurationParameters.DisplayConsole } else { $WriteParameters = @{ ShowTime = $true; LogFile = ""; TimeFormat = "yyyy-MM-dd HH:mm:ss" } } if ($Image) { $Template = $Template -replace '<<Image>>', $Image } $Body = "<body>" if ($AddAfterOpening) { $Body += $AddAfterOpening } if (-not $SkipNewLines) { $Template = $Template.Split("`n") if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML <BR> tags...") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "<BR>", " tags." -Color White, Yellow, White, Yellow } foreach ($t in $Template) { $Body += "$t<br>" } } else { $Body += $Template } foreach ($style in $FormattingParameters.Styles.GetEnumerator()) { foreach ($value in $style.Value) { if ($value -eq "") { continue } if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($style.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($style.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow } $Body = $Body.Replace($value, "<$($style.Name)>$value</$($style.Name)>") } } foreach ($color in $FormattingParameters.Colors.GetEnumerator()) { foreach ($value in $color.Value) { if ($value -eq "") { continue } if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding HTML $($color.Name) tag for $value.") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "$($color.Name)", " tag for ", "$value", ' tags...' -Color White, Yellow, White, Yellow, White, Yellow } $Body = $Body.Replace($value, "<span style=color:$($color.Name)>$value</span>") } } foreach ($links in $FormattingParameters.Links.GetEnumerator()) { foreach ($link in $links.Value) { if ($link.Link -like "*@*") { if ($Logger) { $Logger.AddInfoRecord("Preparing template - adding EMAIL Links for $($links.Key).") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " EMAIL ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White } $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='mailto:$($link.Link)?subject=$($Link.Subject)'>$($Link.Text)</a></span>" } else { if ($Logger) { $Logger.AddInfoRecord("[i] Preparing template - adding HTML Links for $($links.Key)") } else { Write-Color @WriteParameters -Text "[i] Preparing template ", "adding", " HTML ", "Links for", " $($links.Key)..." -Color White, Yellow, White, White, Yellow, White } $Body = $Body -replace "<<$($links.Key)>>", "<span style=color:$($link.Color)><a href='$($link.Link)'>$($Link.Text)</a></span>" } } } if ($AddAfterOpening) { $Body += $AddBeforeClosing } $Body += '</body>' if ($ConfigurationParameters) { if ($ConfigurationParameters.DisplayTemplateHTML -eq $true) { Get-HTML($Body) } } return $Body } function Set-EmailHead { <# .SYNOPSIS Sets the HTML head section for an email with specified formatting options. .DESCRIPTION The Set-EmailHead function generates the HTML head section for an email with customizable formatting options. It includes meta tags for content type, viewport settings, and description. Additionally, it defines styles for the body, tables, headings, lists, and more. .PARAMETER FormattingOptions Specifies the formatting options to be applied to the email content. .EXAMPLE $formatting = @{ FontFamily = 'Arial'; FontSize = '12px'; FontTableDataFamily = 'Arial'; FontTableDataSize = '10px'; FontTableHeadingFamily = 'Arial'; FontTableHeadingSize = '12px'; } Set-EmailHead -FormattingOptions $formatting #> [cmdletBinding()] param( [System.Collections.IDictionary] $FormattingOptions ) $head = @" <!DOCTYPE html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta content="width=device-width, initial-scale=1" name="viewport"> <meta name="description" content="Password Expiration Email"> <style> BODY { background-color: white; font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } TABLE { border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse; font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } TH { border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #00297A; color: white; font-family: $($FormattingOptions.FontTableHeadingFamily); font-size: $($FormattingOptions.FontTableHeadingSize); } TR { font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } UL { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } LI { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } TD { border-width: 1px; padding-right: 2px; padding-left: 2px; padding-top: 0px; padding-bottom: 0px; border-style: solid; border-color: black; background-color: white; font-family: $($FormattingOptions.FontTableDataFamily); font-size: $($FormattingOptions.FontTableDataSize); } H2 { font-family: $($FormattingOptions.FontHeadingFamily); font-size: $($FormattingOptions.FontHeadingSize); } P { font-family: $($FormattingOptions.FontFamily); font-size: $($FormattingOptions.FontSize); } </style> </head> "@ return $Head } function Set-EmailReportBranding { <# .SYNOPSIS Sets the branding for the email report. .DESCRIPTION This function sets the branding for the email report by customizing the company logo and link. .PARAMETER FormattingParameters Specifies the formatting options for the email report branding. .EXAMPLE $brandingParams = @{ CompanyBranding = @{ Link = "https://www.example.com" Inline = $true Logo = "C:\CompanyLogo.png" Width = "200px" Height = "100px" } } Set-EmailReportBranding -FormattingParameters $brandingParams #> [cmdletBinding()] param( [alias('FormattingOptions')] $FormattingParameters ) if ($FormattingParameters.CompanyBranding.Link) { $Report = "<a style=`"text-decoration:none`" href=`"$($FormattingParameters.CompanyBranding.Link)`" class=`"clink logo-container`">" } else { $Report = '' } if ($FormattingParameters.CompanyBranding.Inline) { $Report += "<img width=<fix> height=<fix> src=`"cid:logo`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } else { $Report += "<img width=<fix> height=<fix> src=`"$($FormattingParameters.CompanyBranding.Logo)`" border=`"0`" class=`"company-logo`" alt=`"company-logo`"></a>" } if ($FormattingParameters.CompanyBranding.Width -ne "") { $Report = $Report -replace "width=<fix>", "width=$($FormattingParameters.CompanyBranding.Width)" } else { $Report = $Report -replace "width=<fix>", "" } if ($FormattingParameters.CompanyBranding.Height -ne "") { $Report = $Report -replace "height=<fix>", "height=$($FormattingParameters.CompanyBranding.Height)" } else { $Report = $Report -replace "height=<fix>", "" } return $Report } function Set-EmailWordReplacements($Body, $Replace, $ReplaceWith, [switch] $RegEx) { <# .SYNOPSIS Replaces words or patterns in an email body with specified replacements. .DESCRIPTION This function replaces words or patterns in the email body with specified replacements. It provides the option to use regular expressions for more complex replacements. .PARAMETER Body The email body where the word replacements will be applied. .PARAMETER Replace The word or pattern to be replaced in the email body. .PARAMETER ReplaceWith The replacement for the word or pattern. .PARAMETER RegEx Indicates whether to use regular expressions for replacements. .EXAMPLE $body = "Hello, my name is John." Set-EmailWordReplacements -Body $body -Replace 'John' -ReplaceWith 'Jane' # This will replace "John" with "Jane" in the email body. .EXAMPLE $body = "The cat sat on the mat." Set-EmailWordReplacements -Body $body -Replace 'cat' -ReplaceWith 'dog' -RegEx # This will replace "cat" with "dog" using regular expressions in the email body. #> if ($RegEx) { $Body = $Body -Replace $Replace, $ReplaceWith } else { $Body = $Body.Replace($Replace, $ReplaceWith) } return $Body } function Set-EmailWordReplacementsHash { <# .SYNOPSIS Replaces words in an email body based on a given hash table of substitutions. .DESCRIPTION This function replaces words in the email body with specified substitutions using a hash table. .PARAMETER Body The email body where the word replacements will be applied. .PARAMETER Substitute A hash table containing the words to be replaced as keys and their corresponding substitutions as values. .EXAMPLE $body = "Hello, my name is John." $substitutions = @{ "John" = "Jane" } Set-EmailWordReplacementsHash -Body $body -Substitute $substitutions # This will replace "John" with "Jane" in the email body. #> [CmdletBinding()] param ( $Body, $Substitute ) foreach ($Key in $Substitute.Keys) { Write-Verbose "Set-EmailWordReplacementsHash - Key: $Key Value: $($Substitute.$Key)" $Body = Set-EmailWordReplacements -Body $Body -Replace $Key -ReplaceWith $Substitute.$Key } return $Body } function Get-FileEncoding { <# .SYNOPSIS Get the encoding of a file (ASCII, UTF8, UTF8BOM, Unicode, BigEndianUnicode, UTF7) .DESCRIPTION Get the encoding of a file (ASCII, UTF8, UTF8BOM, Unicode, BigEndianUnicode, UTF7). Encoding is determined by the first few bytes of the file or by the presence of non-ASCII characters. .PARAMETER Path Path to the file to check .EXAMPLE Get-FileEncoding -Path 'C:\Users\pklys\Desktop\test.txt' .NOTES General notes #> [CmdletBinding()] param ( [Parameter(Mandatory = $true, Position = 0)] [string] $Path ) if (-not (Test-Path -Path $Path)) { if ($ErrorActionPreference -eq 'Stop') { throw "Get-FileEncoding - File not found: $Path" } Write-Warning -Message "Get-FileEncoding - File not found: $Path" return } $fileStream = [System.IO.FileStream]::new($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) $byte = [byte[]]::new(4) $null = $fileStream.Read($byte, 0, 4) $fileStream.Close() if ($byte[0] -eq 0xef -and $byte[1] -eq 0xbb -and $byte[2] -eq 0xbf) { return 'UTF8BOM' } elseif ($byte[0] -eq 0xff -and $byte[1] -eq 0xfe) { return 'Unicode' } elseif ($byte[0] -eq 0xfe -and $byte[1] -eq 0xff) { return 'BigEndianUnicode' } elseif ($byte[0] -eq 0x2b -and $byte[1] -eq 0x2f -and $byte[2] -eq 0x76) { return 'UTF7' } else { $fileStream = [System.IO.FileStream]::new($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read) $byte = [byte[]]::new(1) while ($fileStream.Read($byte, 0, 1) -gt 0) { if ($byte[0] -gt 0x7F) { $fileStream.Close() return 'UTF8' } } $fileStream.Close() return 'ASCII' } } function Get-FileInformation { <# .SYNOPSIS Get information about file such as Name, FullName and Size .DESCRIPTION Get information about file such as Name, FullName and Size .PARAMETER File File to get information about .EXAMPLE Get-FileInformation -File 'C:\Support\GitHub\PSSharedGoods\Public\FilesFolders\Get-FileInformation.ps1' #> [CmdletBinding()] param( [alias('LiteralPath', 'Path')][string] $File ) if (Test-Path -LiteralPath $File) { $Item = Get-Item -LiteralPath $File [PSCustomObject] @{ Name = $Item.Name FullName = $Item.FullName Size = Get-FileSize -Bytes $Item.Length IsReadOnly = $Item.IsReadOnly LastWriteTime = $Item.LastWriteTime } } } function Get-FileMetaData { <# .SYNOPSIS Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file .DESCRIPTION Small function that gets metadata information from file providing similar output to what Explorer shows when viewing file .PARAMETER File FileName or FileObject .EXAMPLE Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties .EXAMPLE Get-ChildItem -Path $Env:USERPROFILE\Desktop -Force | Where-Object { $_.Attributes -like '*Hidden*' } | Get-FileMetaData | Out-HtmlView -ScrollX -Filtering -AllProperties #> [CmdletBinding()] param ( [Parameter(Position = 0, ValueFromPipeline)][Object] $File, [ValidateSet('None', 'MACTripleDES', 'MD5', 'RIPEMD160', 'SHA1', 'SHA256', 'SHA384', 'SHA512')][string] $HashAlgorithm = 'None', [switch] $Signature, [switch] $AsHashTable ) Process { foreach ($F in $File) { $MetaDataObject = [ordered] @{} if ($F -is [string]) { if ($F -and (Test-Path -LiteralPath $F)) { $FileInformation = Get-ItemProperty -Path $F if ($FileInformation -is [System.IO.DirectoryInfo]) { continue } } else { Write-Warning "Get-FileMetaData - Doesn't exists. Skipping $F." continue } } elseif ($F -is [System.IO.DirectoryInfo]) { continue } elseif ($F -is [System.IO.FileInfo]) { $FileInformation = $F } else { Write-Warning "Get-FileMetaData - Only files are supported. Skipping $F." continue } $ShellApplication = New-Object -ComObject Shell.Application $ShellFolder = $ShellApplication.Namespace($FileInformation.Directory.FullName) $ShellFile = $ShellFolder.ParseName($FileInformation.Name) $MetaDataProperties = [ordered] @{} 0..400 | ForEach-Object -Process { $DataValue = $ShellFolder.GetDetailsOf($null, $_) $PropertyValue = (Get-Culture).TextInfo.ToTitleCase($DataValue.Trim()).Replace(' ', '') if ($PropertyValue -ne '') { $MetaDataProperties["$_"] = $PropertyValue } } foreach ($Key in $MetaDataProperties.Keys) { $Property = $MetaDataProperties[$Key] $Value = $ShellFolder.GetDetailsOf($ShellFile, [int] $Key) if ($Property -in 'Attributes', 'Folder', 'Type', 'SpaceFree', 'TotalSize', 'SpaceUsed') { continue } If (($null -ne $Value) -and ($Value -ne '')) { $MetaDataObject["$Property"] = $Value } } if ($FileInformation.VersionInfo) { $SplitInfo = ([string] $FileInformation.VersionInfo).Split([char]13) foreach ($Item in $SplitInfo) { $Property = $Item.Split(":").Trim() if ($Property[0] -and $Property[1] -ne '') { if ($Property[1] -in 'False', 'True') { $MetaDataObject["$($Property[0])"] = [bool] $Property[1] } else { $MetaDataObject["$($Property[0])"] = $Property[1] } } } } $MetaDataObject["Attributes"] = $FileInformation.Attributes $MetaDataObject['IsReadOnly'] = $FileInformation.IsReadOnly $MetaDataObject['IsHidden'] = $FileInformation.Attributes -like '*Hidden*' $MetaDataObject['IsSystem'] = $FileInformation.Attributes -like '*System*' if ($Signature) { $DigitalSignature = Get-AuthenticodeSignature -FilePath $FileInformation.Fullname $MetaDataObject['SignatureCertificateSubject'] = $DigitalSignature.SignerCertificate.Subject $MetaDataObject['SignatureCertificateIssuer'] = $DigitalSignature.SignerCertificate.Issuer $MetaDataObject['SignatureCertificateSerialNumber'] = $DigitalSignature.SignerCertificate.SerialNumber $MetaDataObject['SignatureCertificateNotBefore'] = $DigitalSignature.SignerCertificate.NotBefore $MetaDataObject['SignatureCertificateNotAfter'] = $DigitalSignature.SignerCertificate.NotAfter $MetaDataObject['SignatureCertificateThumbprint'] = $DigitalSignature.SignerCertificate.Thumbprint $MetaDataObject['SignatureStatus'] = $DigitalSignature.Status $MetaDataObject['IsOSBinary'] = $DigitalSignature.IsOSBinary } if ($HashAlgorithm -ne 'None') { $MetaDataObject[$HashAlgorithm] = (Get-FileHash -LiteralPath $FileInformation.FullName -Algorithm $HashAlgorithm).Hash } if ($AsHashTable) { $MetaDataObject } else { [PSCustomObject] $MetaDataObject } } } } function Get-FileName { <# .SYNOPSIS Generates a temporary file name with the specified extension. .DESCRIPTION This function generates a temporary file name based on the provided extension. It can generate a temporary file name in the system's temporary folder or just the file name itself. .PARAMETER Extension Specifies the extension for the temporary file name. Default is 'tmp'. .PARAMETER Temporary Indicates whether to generate a temporary file name in the system's temporary folder. .PARAMETER TemporaryFileOnly Indicates whether to generate only the temporary file name without the path. .EXAMPLE Get-FileName -Temporary Generates a temporary file name in the system's temporary folder. Example output: 3ymsxvav.tmp .EXAMPLE Get-FileName -Temporary Generates a temporary file name without the path. Example output: tmpD74C.tmp .EXAMPLE Get-FileName -Temporary -Extension 'xlsx' Generates a temporary file name with the specified extension in the system's temporary folder. Example output: tmp45B6.xlsx .NOTES These examples demonstrate how to use the Get-FileName function to generate temporary file names. #> [CmdletBinding()] param( [string] $Extension = 'tmp', [switch] $Temporary, [switch] $TemporaryFileOnly ) if ($Temporary) { return [io.path]::Combine([System.IO.Path]::GetTempPath(), "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension") } if ($TemporaryFileOnly) { return "$($([System.IO.Path]::GetRandomFileName()).Split('.')[0]).$Extension" } } function Get-FileOwner { <# .SYNOPSIS Retrieves the owner of the specified file or folder. .DESCRIPTION This function retrieves the owner of the specified file or folder. It provides options to resolve the owner's identity and output the results as a hashtable or custom object. .PARAMETER Path Specifies the path to the file or folder. .PARAMETER Recursive Indicates whether to search for files recursively in subdirectories. .PARAMETER JustPath Specifies if only the path information should be returned. .PARAMETER Resolve Indicates whether to resolve the owner's identity. .PARAMETER AsHashTable Specifies if the output should be in hashtable format. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" Retrieves the owner of the specified file "File.txt". .EXAMPLE Get-FileOwner -Path "C:\Example" -Recursive Retrieves the owners of all files in the "Example" directory and its subdirectories. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" -Resolve Retrieves the owner of the specified file "File.txt" and resolves the owner's identity. .EXAMPLE Get-FileOwner -Path "C:\Example\File.txt" -AsHashTable Retrieves the owner of the specified file "File.txt" and outputs the result as a hashtable. #> [cmdletBinding()] param( [Array] $Path, [switch] $Recursive, [switch] $JustPath, [switch] $Resolve, [switch] $AsHashTable ) Begin { } Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $ACL = Get-Acl -Path $_ $Object = [ordered]@{ FullName = $_ Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -Force | ForEach-Object -Process { $File = $_ $ACL = Get-Acl -Path $File.FullName $Object = [ordered] @{ FullName = $_.FullName Extension = $_.Extension CreationTime = $_.CreationTime LastAccessTime = $_.LastAccessTime LastWriteTime = $_.LastWriteTime Attributes = $_.Attributes Owner = $ACL.Owner } if ($Resolve) { $Identity = Convert-Identity -Identity $ACL.Owner if ($Identity) { $Object['OwnerName'] = $Identity.Name $Object['OwnerSid'] = $Identity.SID $Object['OwnerType'] = $Identity.Type } else { $Object['OwnerName'] = '' $Object['OwnerSid'] = '' $Object['OwnerType'] = '' } } if ($AsHashTable) { $Object } else { [PSCustomObject] $Object } } } } } } End { } } function Get-FilePermission { <# .SYNOPSIS Retrieves and displays file permissions for the specified file or folder. .DESCRIPTION This function retrieves and displays the file permissions for the specified file or folder. It provides options to filter permissions based on inheritance, resolve access control types, and include extended information. .EXAMPLE Get-FilePermission -Path "C:\Example\File.txt" Description: Retrieves and displays the permissions for the "File.txt" file. .EXAMPLE Get-FilePermission -Path "D:\Folder" -Inherited Description: Retrieves and displays only the inherited permissions for the "Folder" directory. .EXAMPLE Get-FilePermission -Path "E:\Document.docx" -ResolveTypes -Extended Description: Retrieves and displays the resolved access control types and extended information for the "Document.docx" file. .NOTES This function supports various options to customize the output and handle different permission scenarios. #> [alias('Get-PSPermissions', 'Get-FilePermissions')] [cmdletBinding()] param( [Array] $Path, [switch] $Inherited, [switch] $NotInherited, [switch] $ResolveTypes, [switch] $Extended, [switch] $IncludeACLObject, [switch] $AsHashTable, [System.Security.AccessControl.FileSystemSecurity] $ACLS ) foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $TestPath = Test-Path -Path $FullPath if ($TestPath) { if (-not $ACLS) { try { $ACLS = (Get-Acl -Path $FullPath -ErrorAction Stop) } catch { Write-Warning -Message "Get-FilePermission - Can't access $FullPath. Error $($_.Exception.Message)" continue } } $Output = foreach ($ACL in $ACLS.Access) { if ($Inherited) { if ($ACL.IsInherited -eq $false) { continue } } if ($NotInherited) { if ($ACL.IsInherited -eq $true) { continue } } $TranslateRights = Convert-GenericRightsToFileSystemRights -OriginalRights $ACL.FileSystemRights $ReturnObject = [ordered] @{ } $ReturnObject['Path' ] = $FullPath $ReturnObject['AccessControlType'] = $ACL.AccessControlType if ($ResolveTypes) { $Identity = Convert-Identity -Identity $ACL.IdentityReference if ($Identity) { $ReturnObject['Principal'] = $ACL.IdentityReference $ReturnObject['PrincipalName'] = $Identity.Name $ReturnObject['PrincipalSid'] = $Identity.Sid $ReturnObject['PrincipalType'] = $Identity.Type } else { $ReturnObject['Principal'] = $Identity $ReturnObject['PrincipalName'] = '' $ReturnObject['PrincipalSid'] = '' $ReturnObject['PrincipalType'] = '' } } else { $ReturnObject['Principal'] = $ACL.IdentityReference.Value } $ReturnObject['FileSystemRights'] = $TranslateRights $ReturnObject['IsInherited'] = $ACL.IsInherited if ($Extended) { $ReturnObject['InheritanceFlags'] = $ACL.InheritanceFlags $ReturnObject['PropagationFlags'] = $ACL.PropagationFlags } if ($IncludeACLObject) { $ReturnObject['ACL'] = $ACL $ReturnObject['AllACL'] = $ACLS } if ($AsHashTable) { $ReturnObject } else { [PSCustomObject] $ReturnObject } } $Output } else { Write-Warning "Get-PSPermissions - Path $Path doesn't exists. Skipping." } } } function Get-FilesInFolder { <# .SYNOPSIS Retrieves a list of files in a specified folder with the option to filter by extension. .DESCRIPTION This function retrieves a list of files in the specified folder. By default, it includes all files with the '.evtx' extension, but you can specify a different extension using the $Extension parameter. .PARAMETER Folder Specifies the folder path from which to retrieve files. .PARAMETER Extension Specifies the file extension to filter by. Default value is '*.evtx'. .EXAMPLE Get-FilesInFolder -Folder "C:\Logs" Description: Retrieves all files with the '.evtx' extension in the "C:\Logs" folder. .EXAMPLE Get-FilesInFolder -Folder "D:\Documents" -Extension '*.txt' Description: Retrieves all files with the '.txt' extension in the "D:\Documents" folder. #> [CmdletBinding()] param( [string] $Folder, [string] $Extension = '*.evtx' ) $Files = Get-ChildItem -Path $Folder -Filter $Extension -Recurse $ReturnFiles = foreach ($File in $Files) { $File.FullName } return $ReturnFiles } function Get-FileSize { <# .SYNOPSIS Get-FileSize function calculates the file size in human-readable format. .DESCRIPTION This function takes a file size in bytes and converts it into a human-readable format (e.g., KB, MB, GB, etc.). .PARAMETER Bytes Specifies the size of the file in bytes. .EXAMPLE Get-FileSize -Bytes 1024 Output: 1 KB .EXAMPLE Get-FileSize -Bytes 1048576 Output: 1 MB #> [CmdletBinding()] param( $Bytes ) $sizes = 'Bytes,KB,MB,GB,TB,PB,EB,ZB' -split ',' for ($i = 0; ($Bytes -ge 1kb) -and ($i -lt $sizes.Count); $i++) { $Bytes /= 1kb } $N = 2; if ($i -eq 0) { $N = 0 } return "{0:N$($N)} {1}" -f $Bytes, $sizes[$i] } function Get-PathSeparator { <# .SYNOPSIS Gets the path separator character used by the operating system. .DESCRIPTION This function retrieves the path separator character used by the operating system. It can be useful for handling file paths in a platform-independent manner. .EXAMPLE Get-PathSeparator Output: \ .NOTES The function uses [System.IO.Path]::PathSeparator to get the path separator character. #> [CmdletBinding()] param() return [IO.Path]::PathSeparator } function Get-PathTemporary { <# .SYNOPSIS Gets the path to the temporary directory. .DESCRIPTION This function retrieves the path to the system's temporary directory. .EXAMPLE Get-PathTemporary Output: C:\Users\Username\AppData\Local\Temp .NOTES The function uses [System.IO.Path]::GetTempPath() to get the temporary directory path. #> [CmdletBinding()] param() return [IO.path]::GetTempPath() } function Get-TemporaryDirectory { <# .SYNOPSIS Creates a temporary directory and returns its path. .DESCRIPTION This function generates a temporary directory with a unique name and returns the full path to the directory. .EXAMPLE $tempDir = Get-TemporaryDirectory $tempDir Output: C:\Users\Username\AppData\Local\Temp\abcde12345 .NOTES The temporary directory is created using a random string name with specified characteristics. #> param( ) $TemporaryFolder = Get-RandomStringName -Size 13 -LettersOnly -ToLower $TemporaryPath = [system.io.path]::GetTempPath() $Output = New-Item -ItemType Directory -Path $TemporaryPath -Name $TemporaryFolder -Force if (Test-Path -LiteralPath $Output.FullName) { $Output } } function Remove-FilePermission { <# .SYNOPSIS Removes specific or all access rules for a user or group from a file or folder. .DESCRIPTION This function removes specific or all access rules for a specified user or group from a file or folder. It can be used to manage file permissions effectively. .PARAMETER Path Specifies the path of the file or folder from which to remove access rules. .PARAMETER UserOrGroup Specifies the user or group for which access rules should be removed. If not specified, all access rules will be removed. .PARAMETER All Indicates whether all access rules for the specified file or folder should be removed. .EXAMPLE Remove-FilePermission -Path "C:\Example\File.txt" -UserOrGroup "User1" Removes access rules for "User1" from the file "File.txt". .EXAMPLE Remove-FilePermission -Path "C:\Example\Folder" -All Removes all access rules from the folder "Folder". #> [cmdletBinding()] param( [string] $Path, [string] $UserOrGroup = "", [switch] $All ) $ACL = Get-Acl -Path $Path if ($UserOrGroup -ne "") { foreach ($access in $ACL.Access) { if ($access.IdentityReference.Value -eq $UserOrGroup) { $ACL.RemoveAccessRule($access) | Out-Null } } } if ($All -eq $true) { foreach ($access in $ACL.Access) { $ACL.RemoveAccessRule($access) | Out-Null } } Set-Acl -Path $Path -AclObject $ACL } function Set-FileInheritance { <# .SYNOPSIS Sets or removes inheritance for a specified directory. .DESCRIPTION This function allows you to set or remove inheritance for a specified directory. You can choose to disable inheritance and optionally keep the inherited ACL. .PARAMETER StartingDir Specifies the directory for which to set or remove inheritance. .PARAMETER DisableInheritance Switch parameter to disable inheritance for the specified directory. .PARAMETER KeepInheritedAcl Switch parameter to keep the inherited ACL when disabling inheritance. .EXAMPLE Set-FileInheritance -StartingDir "C:\Example" -DisableInheritance Disables inheritance for the directory "C:\Example". .EXAMPLE Set-FileInheritance -StartingDir "D:\Data" -DisableInheritance -KeepInheritedAcl Disables inheritance for the directory "D:\Data" and keeps the inherited ACL. #> [cmdletBinding()] param( [string] $StartingDir, [switch] $DisableInheritance, [switch] $KeepInheritedAcl ) $acl = Get-Acl -Path $StartingDir $acl.SetAccessRuleProtection($DisableInheritance, $KeepInheritedAcl) $acl | Set-Acl -Path $StartingDir } function Set-FileOwner { <# .SYNOPSIS Sets the owner of a file or folder. .DESCRIPTION This function sets the owner of a specified file or folder to the provided owner. .PARAMETER Path Specifies the path to the file or folder. .PARAMETER Recursive Indicates whether to process the items in the specified path recursively. .PARAMETER Owner Specifies the new owner for the file or folder. .PARAMETER Exclude Specifies an array of owners to exclude from ownership change. .PARAMETER JustPath Indicates whether to only change the owner of the specified path without recursing into subfolders. .EXAMPLE Set-FileOwner -Path "C:\Example\File.txt" -Owner "DOMAIN\User1" Description: Sets the owner of the file "File.txt" to "DOMAIN\User1". .EXAMPLE Set-FileOwner -Path "C:\Example\Folder" -Owner "DOMAIN\User2" -Recursive Description: Sets the owner of the folder "Folder" and all its contents to "DOMAIN\User2" recursively. #> [cmdletBinding(SupportsShouldProcess)] param( [Array] $Path, [switch] $Recursive, [string] $Owner, [string[]] $Exlude, [switch] $JustPath ) Begin { } Process { foreach ($P in $Path) { if ($P -is [System.IO.FileSystemInfo]) { $FullPath = $P.FullName } elseif ($P -is [string]) { $FullPath = $P } $OwnerTranslated = [System.Security.Principal.NTAccount]::new($Owner) if ($FullPath -and (Test-Path -Path $FullPath)) { if ($JustPath) { $FullPath | ForEach-Object -Process { $File = $_ try { $ACL = Get-Acl -Path $File -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File -AclObject $ACL -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } } else { Get-ChildItem -LiteralPath $FullPath -Recurse:$Recursive -ErrorAction SilentlyContinue -ErrorVariable err | ForEach-Object -Process { $File = $_ try { $ACL = Get-Acl -Path $File.FullName -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Getting ACL failed with error: $($_.Exception.Message)" } if ($ACL.Owner -notin $Exlude -and $ACL.Owner -ne $OwnerTranslated) { if ($PSCmdlet.ShouldProcess($File.FullName, "Replacing owner $($ACL.Owner) to $OwnerTranslated")) { try { $ACL.SetOwner($OwnerTranslated) Set-Acl -Path $File.FullName -AclObject $ACL -ErrorAction Stop } catch { Write-Warning "Set-FileOwner - Replacing owner $($ACL.Owner) to $OwnerTranslated failed with error: $($_.Exception.Message)" } } } } foreach ($e in $err) { Write-Warning "Set-FileOwner - Errors processing $($e.Exception.Message) ($($e.CategoryInfo.Reason))" } } } } } End { } } function Set-FilePermission { <# .SYNOPSIS Sets file permissions for a specified user or group on a given path. .DESCRIPTION This function sets file permissions for a specified user or group on a given path. It allows you to define the type of access control, inheritance flags, and propagation flags. .PARAMETER Path The path to the file or directory for which permissions need to be set. .PARAMETER Principal Specifies the user or group for which permissions are being set. Use the format 'domain\username'. .PARAMETER InheritedFolderPermissions Specifies the inheritance flags for folder permissions. Default values are ContainerInherit and ObjectInherit. .PARAMETER AccessControlType Specifies the type of access control to be allowed or denied. Default is Allow. .PARAMETER PropagationFlags Specifies how the access control entries are propagated to child objects. Default is None. .PARAMETER AclRightsToAssign Specifies the file system rights to assign to the user or group. .EXAMPLE Set-FilePermission -Path "C:\Example\File.txt" -Principal "domain\username" -AclRightsToAssign "Modify" Sets Modify permissions for the user 'domain\username' on the file "File.txt" located at "C:\Example". .EXAMPLE Set-FilePermission -Path "D:\Data" -Principal "domain\group" -AclRightsToAssign "FullControl" -InheritedFolderPermissions @("ContainerInherit") Sets FullControl permissions for the group 'domain\group' on the directory "Data" located at "D:\" with inheritance only for subfolders. .NOTES File permissions are set using the Set-Acl cmdlet from the System.Security.AccessControl module. #> [cmdletBinding()] param ( [string] $Path, [alias('UserOrGroup')][string] $Principal, [System.Security.AccessControl.InheritanceFlags] $InheritedFolderPermissions = @( [System.Security.AccessControl.InheritanceFlags]::ContainerInherit, [System.Security.AccessControl.InheritanceFlags]::ObjectInherit ), [System.Security.AccessControl.AccessControlType] $AccessControlType = [System.Security.AccessControl.AccessControlType]::Allow, [System.Security.AccessControl.PropagationFlags] $PropagationFlags = [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.FileSystemRights] $AclRightsToAssign ) if ($Principal) { $User = [System.Security.Principal.NTAccount]::new($Principal) $ACL = Get-Acl -Path $Path $Rule = [System.Security.AccessControl.FileSystemAccessRule]::new($User, $AclRightsToAssign, $InheritedFolderPermissions, $PropagationFlags, $AccessControlType) $ACL.SetAccessRule($Rule) Try { Set-Acl -Path $Path -AclObject $ACL } catch { Write-Warning "Set-FilePermission - Setting permission $AclRightsToAssign failed with error: $($_.Exception.Message)" } } } function Get-GitHubLatestRelease { <# .SYNOPSIS Gets one or more releases from GitHub repository .DESCRIPTION Gets one or more releases from GitHub repository .PARAMETER Url Url to github repository .EXAMPLE Get-GitHubLatestRelease -Url "https://api.github.com/repos/evotecit/Testimo/releases" | Format-Table .NOTES General notes #> [CmdLetBinding()] param( [parameter(Mandatory)][alias('ReleasesUrl')][uri] $Url ) $ProgressPreference = 'SilentlyContinue' $Responds = Test-Connection -ComputerName $URl.Host -Quiet -Count 1 if ($Responds) { Try { [Array] $JsonOutput = (Invoke-WebRequest -Uri $Url -ErrorAction Stop | ConvertFrom-Json) foreach ($JsonContent in $JsonOutput) { [PSCustomObject] @{ PublishDate = [DateTime] $JsonContent.published_at CreatedDate = [DateTime] $JsonContent.created_at PreRelease = [bool] $JsonContent.prerelease Version = [version] ($JsonContent.name -replace 'v', '') Tag = $JsonContent.tag_name Branch = $JsonContent.target_commitish Errors = '' } } } catch { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = $_.Exception.Message } } } else { [PSCustomObject] @{ PublishDate = $null CreatedDate = $null PreRelease = $null Version = $null Tag = $null Branch = $null Errors = "No connection (ping) to $($Url.Host)" } } $ProgressPreference = 'Continue' } function Get-GitHubVersion { <# .SYNOPSIS Get the latest version of a GitHub repository and compare with local version .DESCRIPTION Get the latest version of a GitHub repository and compare with local version .PARAMETER Cmdlet Cmdlet to find module for .PARAMETER RepositoryOwner Repository owner .PARAMETER RepositoryName Repository name .EXAMPLE Get-GitHubVersion -Cmdlet 'Start-DelegationModel' -RepositoryOwner 'evotecit' -RepositoryName 'DelegationModel' .NOTES General notes #> [cmdletBinding()] param( [Parameter(Mandatory)][string] $Cmdlet, [Parameter(Mandatory)][string] $RepositoryOwner, [Parameter(Mandatory)][string] $RepositoryName ) $App = Get-Command -Name $Cmdlet -ErrorAction SilentlyContinue if ($App) { [Array] $GitHubReleases = (Get-GitHubLatestRelease -Url "https://api.github.com/repos/$RepositoryOwner/$RepositoryName/releases" -Verbose:$false) $LatestVersion = $GitHubReleases[0] if (-not $LatestVersion.Errors) { if ($App.Version -eq $LatestVersion.Version) { "Current/Latest: $($LatestVersion.Version) at $($LatestVersion.PublishDate)" } elseif ($App.Version -lt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Update?" } elseif ($App.Version -gt $LatestVersion.Version) { "Current: $($App.Version), Published: $($LatestVersion.Version) at $($LatestVersion.PublishDate). Lucky you!" } } else { "Current: $($App.Version)" } } else { "Current: Unknown" } } function Get-IPAddressRangeInformation { <# .SYNOPSIS Provides information about IP Address range .DESCRIPTION Provides information about IP Address range .PARAMETER Network Network in form of IP/NetworkLength (e.g. 10.2.10.0/24') .PARAMETER IPAddress IP Address to use .PARAMETER NetworkLength Network length to use .PARAMETER CIDRObject CIDRObject to use .EXAMPLE $CidrObject = @{ Ip = '10.2.10.0' NetworkLength = 24 } Get-IPAddressRangeInformation -CIDRObject $CidrObject | Format-Table .EXAMPLE Get-IPAddressRangeInformation -Network '10.2.10.0/24' | Format-Table .EXAMPLE Get-IPAddressRangeInformation -IPAddress '10.2.10.0' -NetworkLength 24 | Format-Table .NOTES General notes #> [cmdletBinding(DefaultParameterSetName = 'Network')] param( [Parameter(ParameterSetName = 'Network', Mandatory)][string] $Network, [Parameter(ParameterSetName = 'IPAddress', Mandatory)][string] $IPAddress, [Parameter(ParameterSetName = 'IPAddress', Mandatory)][int] $NetworkLength, [Parameter(ParameterSetName = 'CIDR', Mandatory)][psobject] $CIDRObject ) $IPv4Regex = '(?:(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)\.){3}(?:0?0?\d|0?[1-9]\d|1\d\d|2[0-5][0-5]|2[0-4]\d)' if ($Network) { $CIDRObject = @{ Ip = $Network.Split('/')[0] NetworkLength = $Network.Split('/')[1] } } elseif ($IPAddress -and $NetworkLength) { $CIDRObject = @{ Ip = $IPAddress NetworkLength = $NetworkLength } } elseif ($CIDRObject) { } else { Write-Error "Get-IPAddressRangeInformation - Invalid parameters specified" return } $o = [ordered] @{} $o.IP = [string] $CIDRObject.IP $o.BinaryIP = Convert-IPToBinary $o.IP if (-not $o.BinaryIP) { return } $o.NetworkLength = [int32] $CIDRObject.NetworkLength $o.SubnetMask = Convert-BinaryToIP ('1' * $o.NetworkLength).PadRight(32, '0') $o.BinarySubnetMask = ('1' * $o.NetworkLength).PadRight(32, '0') $o.BinaryNetworkAddress = $o.BinaryIP.SubString(0, $o.NetworkLength).PadRight(32, '0') if ($Contains) { if ($Contains -match "\A${IPv4Regex}\z") { return Test-IPIsInNetwork $Contains $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') } else { Write-Error "Get-IPAddressRangeInformation - Invalid IPv4 address specified with -Contains" return } } $o.NetworkAddress = Convert-BinaryToIP $o.BinaryNetworkAddress if ($o.NetworkLength -eq 32 -or $o.NetworkLength -eq 31) { $o.HostMin = $o.IP } else { $o.HostMin = Convert-BinaryToIP ([System.Convert]::ToString(([System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1), 2)).PadLeft(32, '0') } [string] $BinaryBroadcastIP = $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1') $o.BinaryBroadcast = $BinaryBroadcastIP [int64] $DecimalHostMax = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) - 1 [string] $BinaryHostMax = [System.Convert]::ToString($DecimalHostMax, 2).PadLeft(32, '0') $o.HostMax = Convert-BinaryToIP $BinaryHostMax $o.TotalHosts = [int64][System.Convert]::ToString(([System.Convert]::ToInt64($BinaryBroadcastIP, 2) - [System.Convert]::ToInt64($o.BinaryNetworkAddress, 2) + 1)) $o.UsableHosts = $o.TotalHosts - 2 if ($o.NetworkLength -eq 32) { $o.Broadcast = $Null $o.UsableHosts = [int64] 1 $o.TotalHosts = [int64] 1 $o.HostMax = $o.IP } elseif ($o.NetworkLength -eq 31) { $o.Broadcast = $Null $o.UsableHosts = [int64] 2 $o.TotalHosts = [int64] 2 [int64] $DecimalHostMax2 = [System.Convert]::ToInt64($BinaryBroadcastIP, 2) [string] $BinaryHostMax2 = [System.Convert]::ToString($DecimalHostMax2, 2).PadLeft(32, '0') $o.HostMax = Convert-BinaryToIP $BinaryHostMax2 } elseif ($o.NetworkLength -eq 30) { $o.UsableHosts = [int64] 2 $o.TotalHosts = [int64] 4 $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP } else { $o.Broadcast = Convert-BinaryToIP $BinaryBroadcastIP } if ($Enumerate) { $IPRange = @(Get-IPRange $o.BinaryNetworkAddress $o.BinaryNetworkAddress.SubString(0, $o.NetworkLength).PadRight(32, '1')) if ((31, 32) -notcontains $o.NetworkLength ) { $IPRange = $IPRange[1..($IPRange.Count - 1)] $IPRange = $IPRange[0..($IPRange.Count - 2)] } $o.IPEnumerated = $IPRange } else { $o.IPEnumerated = @() } [PSCustomObject]$o } function Get-Logger { <# .SYNOPSIS Returns an instance of the logger object. .DESCRIPTION This function creates a logger object that can be used to log messages to a file or console. It allows customization of log file path, log directory, log filename, time display, and time format. .PARAMETER LogPath Specifies the full path of the log file. .PARAMETER LogsDir Specifies the directory where the log file will be stored. .PARAMETER Filename Specifies the name of the log file. .PARAMETER ShowTime Indicates whether to display timestamps in the log messages. .PARAMETER TimeFormat Specifies the format of the timestamp to be displayed in the log messages. .EXAMPLE Creates a logger with a full log name: $Logger = Get-Logger -ShowTime -LogPath 'C:\temp\test.log' $Logger.AddErrorRecord("test error") $Logger.AddInfoRecord("test info") $Logger.AddSuccessRecord("test success") $Logger.AddRecord("test record") .EXAMPLE Creates a logger with a directory name and auto-generated log name: $Logger = Get-Logger -ShowTime -LogsDir 'C:\temp' $Logger.AddErrorRecord("test error") .EXAMPLE Creates a logger with a directory name and a separately defined log filename: $Logger = Get-Logger -ShowTime -LogsDir 'C:\temp' -Filename 'test.log' $Logger.AddErrorRecord("test error") .EXAMPLE Creates a logger without a log file, only for console output: $Logger = Get-Logger -ShowTime $Logger.AddErrorRecord("test error") .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = "All")] param ( [Parameter(Mandatory = $false, ParameterSetName = 'Logpath')][string] $LogPath, [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $LogsDir, [Parameter(Mandatory = $false, ParameterSetName = 'Complexpath')][string] $Filename, [switch] $ShowTime, [string] $TimeFormat = 'yyyy-MM-dd HH:mm:ss' ) if ($PSCmdlet.ParameterSetName -eq 'Complexpath') { if (-not $Filename) { $CallerName = [System.IO.Path]::GetFileNameWithoutExtension((Split-Path $MyInvocation.PSCommandPath -Leaf)) $Filename = "$([DateTime]::Now.ToString($TimeFormat) -replace('[^.\-\w]', '_'))_$CallerName.log" } $LogPath = Join-Path $LogsDir $Filename } if ($LogPath) { $LogsDir = [System.IO.Path]::GetDirectoryName($LogPath) New-Item $LogsDir -ItemType Directory -Force | Out-Null New-Item $LogPath -ItemType File -Force | Out-Null } $Logger = [PSCustomObject]@{ LogPath = $LogPath ShowTime = $ShowTime TimeFormat = $TimeFormat } Add-Member -InputObject $Logger -MemberType ScriptMethod AddErrorRecord -Value { param( [Parameter(Mandatory = $true)] [string]$String ) if (-not $this.LogPath) { Write-Color -Text "[Error] ", $String -Color Red, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Error] ", $String -Color Red, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddInfoRecord -Value { param( [Parameter(Mandatory = $true)] [string]$String ) if (-not $this.LogPath) { Write-Color -Text "[Info] ", $String -Color Yellow, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Info] ", $String -Color Yellow, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddWarningRecord -Value { param( [Parameter(Mandatory = $true)] [string]$String ) if (-not $this.LogPath) { Write-Color -Text "[Warning] ", $String -Color Magenta, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Warning] ", $String -Color Magenta, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddRecord -Value { param( [Parameter(Mandatory = $true)] [string]$String ) if (-not $this.LogPath) { Write-Color -Text " $String" -Color White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text " $String" -Color White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } Add-Member -InputObject $Logger -MemberType ScriptMethod AddSuccessRecord -Value { param( [Parameter(Mandatory = $true)] [string]$String ) if (-not $this.LogPath) { Write-Color -Text "[Success] ", $String -Color Green, White -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } else { Write-Color -Text "[Success] ", $String -Color Green, White -LogFile:$this.LogPath -ShowTime:$this.ShowTime -TimeFormat $this:TimeFormat } } return $Logger } function Add-ToArray { <# .SYNOPSIS Adds an element to an ArrayList. .DESCRIPTION This function adds an element to the specified ArrayList. .PARAMETER List The ArrayList to which the element will be added. .PARAMETER Element The element to be added to the ArrayList. .EXAMPLE $myList = New-Object System.Collections.ArrayList Add-ToArray -List $myList -Element "Apple" # Adds the string "Apple" to the ArrayList $myList. .EXAMPLE $myList = New-Object System.Collections.ArrayList Add-ToArray -List $myList -Element 42 # Adds the integer 42 to the ArrayList $myList. #> [CmdletBinding()] param( [System.Collections.ArrayList] $List, [Object] $Element ) [void] $List.Add($Element) } function Add-ToArrayAdvanced { <# .SYNOPSIS Adds an element to an ArrayList with advanced options. .DESCRIPTION The Add-ToArrayAdvanced function adds an element to an ArrayList with various options such as skipping null elements, requiring uniqueness, performing full comparison, and merging elements. .PARAMETER List The ArrayList to which the element will be added. .PARAMETER Element The element to be added to the ArrayList. .PARAMETER SkipNull If specified, skips adding null elements to the ArrayList. .PARAMETER RequireUnique If specified, ensures that the element is unique in the ArrayList. .PARAMETER FullComparison If specified with RequireUnique, performs a full comparison of elements before adding. .PARAMETER Merge If specified, merges the element into the ArrayList. .EXAMPLE Add-ToArrayAdvanced -List $myList -Element "Apple" Description: Adds the string "Apple" to the ArrayList $myList. .EXAMPLE Add-ToArrayAdvanced -List $myList -Element "Banana" -RequireUnique -FullComparison Description: Adds the string "Banana" to the ArrayList $myList only if it is not already present, performing a full comparison. #> [CmdletBinding()] param( [System.Collections.ArrayList] $List, [Object] $Element, [switch] $SkipNull, [switch] $RequireUnique, [switch] $FullComparison, [switch] $Merge ) if ($SkipNull -and $null -eq $Element) { return } if ($RequireUnique) { if ($FullComparison) { foreach ($ListElement in $List) { if ($ListElement -eq $Element) { $TypeLeft = Get-ObjectType -Object $ListElement $TypeRight = Get-ObjectType -Object $Element if ($TypeLeft.ObjectTypeName -eq $TypeRight.ObjectTypeName) { return } } } } else { if ($List -contains $Element) { return } } } if ($Merge) { [void] $List.AddRange($Element) } else { [void] $List.Add($Element) } } function Add-ToHashTable($Hashtable, $Key, $Value) { <# .SYNOPSIS Adds a key-value pair to a hashtable. .DESCRIPTION This function adds a key-value pair to a given hashtable. If the value is not null or empty, it is added to the hashtable. .PARAMETER Hashtable The hashtable to which the key-value pair will be added. .PARAMETER Key The key of the key-value pair to be added. .PARAMETER Value The value of the key-value pair to be added. .EXAMPLE $myHashtable = @{} Add-ToHashTable -Hashtable $myHashtable -Key "Name" -Value "John" # Adds the key-value pair "Name"-"John" to $myHashtable. .EXAMPLE $myHashtable = @{} Add-ToHashTable -Hashtable $myHashtable -Key "Age" -Value 25 # Adds the key-value pair "Age"-25 to $myHashtable. #> if ($null -ne $Value -and $Value -ne '') { $Hashtable.Add($Key, $Value) } } function Clear-DataInformation { <# .SYNOPSIS Cleans up data information by removing empty values and specified keys. .DESCRIPTION The Clear-DataInformation function removes empty values and specified keys from the provided data. It iterates through the data structure and removes keys with null values or keys not in the specified types required array. It also removes empty domains and the 'FoundDomains' key if it becomes empty. .PARAMETER Data The data structure containing information to be cleaned. .PARAMETER TypesRequired An array of types that are required to be kept in the data structure. .PARAMETER DontRemoveSupportData A switch parameter to indicate whether to skip removing keys not in TypesRequired. .PARAMETER DontRemoveEmpty A switch parameter to indicate whether to skip removing keys with null values. .EXAMPLE $data = @{ FoundDomains = @{ Domain1 = @{ Key1 = 'Value1' Key2 = $null } Domain2 = @{ Key1 = 'Value1' Key2 = 'Value2' } } } Clear-DataInformation -Data $data -TypesRequired @('Key1') -DontRemoveSupportData This example removes keys with null values from the 'FoundDomains' data structure, keeping only keys of type 'Key1'. #> [CmdletBinding()] param( [System.Collections.IDictionary] $Data, [Array] $TypesRequired, [switch] $DontRemoveSupportData, [switch] $DontRemoveEmpty ) foreach ($Domain in $Data.FoundDomains.Keys) { $RemoveDomainKeys = foreach ($Key in $Data.FoundDomains.$Domain.Keys) { if ($null -eq $Data.FoundDomains.$Domain.$Key) { if (-not $DontRemoveEmpty) { $Key } continue } if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) { $Key } } foreach ($Key in $RemoveDomainKeys) { $Data.FoundDomains.$Domain.Remove($Key) } } $RemoveDomains = foreach ($Domain in $Data.FoundDomains.Keys) { if ($Data.FoundDomains.$Domain.Count -eq 0) { $Domain } } foreach ($Domain in $RemoveDomains) { $Data.FoundDomains.Remove($Domain) } if ($Data.FoundDomains.Count -eq 0) { $Data.Remove('FoundDomains') } $RemoveKeys = foreach ($Key in $Data.Keys) { if ($Key -eq 'FoundDomains') { continue } if ($null -eq $Data.$Key) { if (-not $DontRemoveEmpty) { $Key } continue } if ($Key -notin $TypesRequired -and $DontRemoveSupportData -eq $false) { $Key } } foreach ($Key in $RemoveKeys) { $Data.Remove($Key) } } function Compare-MultipleObjects { <# .SYNOPSIS Compares multiple objects based on specified properties and displays differences. .DESCRIPTION The Compare-MultipleObjects function compares multiple objects based on specified properties and displays differences in a structured format. It provides options to customize the comparison output and handle various scenarios. .PARAMETER Objects Specifies the list of objects to compare. .PARAMETER ObjectsName Specifies an array of names for the objects being compared. .PARAMETER CompareSorted Indicates whether to compare objects in a sorted manner. .PARAMETER FormatOutput Indicates whether to format the output for better readability. .PARAMETER FormatDifferences Indicates whether to format and highlight the differences in the output. .PARAMETER Summary Indicates whether to display a summary of the comparison results. .PARAMETER Splitter Specifies the delimiter to use when joining property values. .PARAMETER Property Specifies the properties to compare across objects. .PARAMETER ExcludeProperty Specifies properties to exclude from the comparison. .PARAMETER AllProperties Indicates whether to compare all properties of the objects. .PARAMETER SkipProperties Indicates whether to skip comparing properties. .PARAMETER First Specifies the number of first objects to consider for comparison. .PARAMETER Last Specifies the number of last objects to consider for comparison. .PARAMETER Replace Specifies replacement values for specific properties. .PARAMETER FlattenObject Indicates whether to flatten the object structure for comparison. .EXAMPLE Compare-MultipleObjects -Objects $objects -Property 'Name', 'Age' -FormatOutput Description: Compares the objects in the $objects array based on the 'Name' and 'Age' properties and formats the output for better readability. .EXAMPLE Compare-MultipleObjects -Objects $objects -Property 'Status' -FormatDifferences Description: Compares the objects in the $objects array based on the 'Status' property and highlights the differences in the output. #> [CmdLetBinding()] param( [System.Collections.IList] $Objects, [Array] $ObjectsName = @(), [switch] $CompareSorted, [switch] $FormatOutput, [switch] $FormatDifferences, [switch] $Summary, [string] $Splitter = ', ', [string[]] $Property, [string[]] $ExcludeProperty, [switch] $AllProperties, [switch] $SkipProperties, [int] $First, [int] $Last, [Array] $Replace, [switch] $FlattenObject ) if ($null -eq $Objects -or $Objects.Count -eq 1) { Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($($Objects.Count))." return } if (-not $ObjectsName) { $ObjectsName = @() } if ($ObjectsName.Count -gt 0 -and $Objects.Count -gt $ObjectsName.Count) { Write-Warning -Message "Compare-MultipleObjects - Unable to rename objects. ObjectsName small then amount of Objects ($($Objects.Count))." } function Compare-TwoArrays { [CmdLetBinding()] param( [string] $FieldName, [Array] $Object1, [Array] $Object2, [Array] $Replace ) $Result = [ordered] @{ Status = $false Same = [System.Collections.Generic.List[string]]::new() Add = [System.Collections.Generic.List[string]]::new() Remove = [System.Collections.Generic.List[string]]::new() } if ($Replace) { foreach ($R in $Replace) { if (($($R.Keys[0]) -eq '') -or ($($R.Keys[0]) -eq $FieldName)) { if ($null -ne $Object1) { $Object1 = $Object1 -replace $($R.Values)[0], $($R.Values)[1] } if ($null -ne $Object2) { $Object2 = $Object2 -replace $($R.Values)[0], $($R.Values)[1] } } } } if ($null -eq $Object1 -and $null -eq $Object2) { $Result['Status'] = $true } elseif (($null -eq $Object1) -or ($null -eq $Object2)) { $Result['Status'] = $false foreach ($O in $Object1) { $Result['Add'].Add($O) } foreach ($O in $Object2) { $Result['Remove'].Add($O) } } else { $ComparedObject = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -IncludeEqual foreach ($_ in $ComparedObject) { if ($_.SideIndicator -eq '==') { $Result['Same'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '<=')) { $Result['Add'].Add($_.InputObject) } elseif (($_.SideIndicator -eq '=>')) { $Result['Remove'].Add($_.InputObject) } } IF ($Result['Add'].Count -eq 0 -and $Result['Remove'].Count -eq 0) { $Result['Status'] = $true } else { $Result['Status'] = $false } } $Result } if ($ObjectsName[0]) { $ValueSourceName = $ObjectsName[0] } else { $ValueSourceName = "Source" } [Array] $Objects = foreach ($Object in $Objects) { if ($null -eq $Object) { [PSCustomObject] @{} } else { $Object } } if ($FlattenObject) { try { [Array] $Objects = ConvertTo-FlatObject -Objects $Objects -ExcludeProperty $ExcludeProperty } catch { Write-Warning "Compare-MultipleObjects - Unable to flatten objects. ($($_.Exception.Message))" } } if ($First -or $Last) { [int] $TotalCount = $First + $Last if ($TotalCount -gt 1) { $Objects = $Objects | Select-Object -First $First -Last $Last } else { Write-Warning "Compare-MultipleObjects - Unable to compare objects. Not enough objects to compare ($TotalCount)." return } } $ReturnValues = @( $FirstElement = [ordered] @{ } $FirstElement['Name'] = 'Properties' if ($Summary) { $FirstElement['Same'] = $null $FirstElement['Different'] = $null } $FirstElement['Status'] = $false $FirstObjectProperties = Select-Properties -Objects $Objects -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties if (-not $SkipProperties) { if ($FormatOutput) { $FirstElement[$ValueSourceName] = $FirstObjectProperties -join $Splitter } else { $FirstElement[$ValueSourceName] = $FirstObjectProperties } [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) { if ($ObjectsName[$i]) { $ValueToUse = $ObjectsName[$i] } else { $ValueToUse = $i } if ($Objects[0] -is [System.Collections.IDictionary]) { [string[]] $CompareObjectProperties = $Objects[$i].Keys } else { [string[]] $CompareObjectProperties = $Objects[$i].PSObject.Properties.Name [string[]] $CompareObjectProperties = Select-Properties -Objects $Objects[$i] -Property $Property -ExcludeProperty $ExcludeProperty -AllProperties:$AllProperties } if ($FormatOutput) { $FirstElement["$ValueToUse"] = $CompareObjectProperties -join $Splitter } else { $FirstElement["$ValueToUse"] = $CompareObjectProperties } if ($CompareSorted) { $Value1 = $FirstObjectProperties | Sort-Object $Value2 = $CompareObjectProperties | Sort-Object } else { $Value1 = $FirstObjectProperties $Value2 = $CompareObjectProperties } $Status = Compare-TwoArrays -FieldName 'Properties' -Object1 $Value1 -Object2 $Value2 -Replace $Replace if ($FormatDifferences) { $FirstElement["$ValueToUse-Add"] = $Status['Add'] -join $Splitter $FirstElement["$ValueToUse-Remove"] = $Status['Remove'] -join $Splitter $FirstElement["$ValueToUse-Same"] = $Status['Same'] -join $Splitter } else { $FirstElement["$ValueToUse-Add"] = $Status['Add'] $FirstElement["$ValueToUse-Remove"] = $Status['Remove'] $FirstElement["$ValueToUse-Same"] = $Status['Same'] } $Status } if ($IsSame.Status -notcontains $false) { $FirstElement['Status'] = $true } else { $FirstElement['Status'] = $false } if ($Summary) { [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split') if ($FormatDifferences) { $FirstElement['Same'] = ($Collection[0] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } ) -join $Splitter $FirstElement['Different'] = ($Collection[1] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } ) -join $Splitter } else { $FirstElement['Same'] = $Collection[0] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } $FirstElement['Different'] = $Collection[1] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } } } [PSCustomObject] $FirstElement } foreach ($NameProperty in $FirstObjectProperties) { $EveryOtherElement = [ordered] @{ } $EveryOtherElement['Name'] = $NameProperty if ($Summary) { $EveryOtherElement['Same'] = $null $EveryOtherElement['Different'] = $null } $EveryOtherElement.Status = $false if ($FormatOutput) { $EveryOtherElement[$ValueSourceName] = $Objects[0].$NameProperty -join $Splitter } else { $EveryOtherElement[$ValueSourceName] = $Objects[0].$NameProperty } [Array] $IsSame = for ($i = 1; $i -lt $Objects.Count; $i++) { $Skip = $false if ($ObjectsName[$i]) { $ValueToUse = $ObjectsName[$i] } else { $ValueToUse = $i } if ($Objects[$i] -is [System.Collections.IDictionary]) { if ($Objects[$i].Keys -notcontains $NameProperty) { $Status = [ordered] @{ Status = $false Same = @() Add = @() Remove = @() } $Skip = $true } } elseif ($Objects[$i].PSObject.Properties.Name -notcontains $NameProperty) { $Status = [ordered] @{ Status = $false; Same = @() Add = @() Remove = @() } $Skip = $true } if ($FormatOutput) { $EveryOtherElement["$ValueToUse"] = $Objects[$i].$NameProperty -join $Splitter } else { $EveryOtherElement["$ValueToUse"] = $Objects[$i].$NameProperty } if ($CompareSorted) { $Value1 = $Objects[0].$NameProperty | Sort-Object $Value2 = $Objects[$i].$NameProperty | Sort-Object } else { $Value1 = $Objects[0].$NameProperty $Value2 = $Objects[$i].$NameProperty } if ($Value1 -is [PSCustomObject]) { [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() } continue } elseif ($Value1 -is [System.Collections.IDictionary]) { [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() } continue } elseif ($Value1 -is [Array] -and $Value1.Count -ne 0 -and $Value1[0] -isnot [string]) { [ordered] @{ Status = $null; Same = @(); Add = @(); Remove = @() } continue } if (-not $Skip) { $Status = Compare-TwoArrays -FieldName $NameProperty -Object1 $Value1 -Object2 $Value2 -Replace $Replace } else { $Status['Add'] = $Value1 } if ($FormatDifferences) { $EveryOtherElement["$ValueToUse-Add"] = $Status['Add'] -join $Splitter $EveryOtherElement["$ValueToUse-Remove"] = $Status['Remove'] -join $Splitter $EveryOtherElement["$ValueToUse-Same"] = $Status['Same'] -join $Splitter } else { $EveryOtherElement["$ValueToUse-Add"] = $Status['Add'] $EveryOtherElement["$ValueToUse-Remove"] = $Status['Remove'] $EveryOtherElement["$ValueToUse-Same"] = $Status['Same'] } $Status } if ($null -eq $IsSame.Status) { $EveryOtherElement['Status'] = $null } elseif ($IsSame.Status -notcontains $false) { $EveryOtherElement['Status'] = $true } else { $EveryOtherElement['Status'] = $false } if ($Summary) { [Array] $Collection = (0..($IsSame.Count - 1)).Where( { $IsSame[$_].Status -eq $true }, 'Split') if ($FormatDifferences) { $EveryOtherElement['Same'] = ($Collection[0] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } ) -join $Splitter $EveryOtherElement['Different'] = ($Collection[1] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } ) -join $Splitter } else { $EveryOtherElement['Same'] = $Collection[0] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } $EveryOtherElement['Different'] = $Collection[1] | ForEach-Object { $Count = $_ + 1 if ($ObjectsName[$Count]) { $ObjectsName[$Count] } else { $Count } } } } [PSCuStomObject] $EveryOtherElement } ) if ($ReturnValues.Count -eq 1) { return , $ReturnValues } else { return $ReturnValues } } function Compare-ObjectsAdvanced { <# .SYNOPSIS Compares two sets of objects based on a specified property. .DESCRIPTION This function compares two sets of objects based on a specified property. It can be used to identify differences between the objects and perform actions accordingly. .PARAMETER Object1 The first set of objects to compare. .PARAMETER Object2 The second set of objects to compare. .PARAMETER CommonProperty Specifies the common property to compare between the objects. Default is 'DistinguishedName'. .PARAMETER AddObjectArrayName An array of names for additional properties to add to Object1. .PARAMETER AddObjectArray An array of values for additional properties to add to Object1. .PARAMETER Object1Property Specifies a property from Object1 to compare. .PARAMETER Object2Property Specifies a property from Object2 to compare. .PARAMETER ObjectPropertySubstitute Specifies a substitute property name for comparison. Default is 'SpecialValueToCompare'. .PARAMETER RemoveSideIndicator Indicates whether to remove side indicators in the comparison results. .PARAMETER KeepTemporaryProperty Indicates whether to keep temporary properties added during comparison. .PARAMETER Side Specifies which side to compare ('Left' or 'Right'). Default is 'Left'. .EXAMPLE Compare-ObjectsAdvanced -Object1 $ObjectSet1 -Object2 $ObjectSet2 -CommonProperty 'Name' -Object1Property 'Size' -Object2Property 'Size' Description: Compares two sets of objects based on the 'Name' property and the 'Size' property from each set. .EXAMPLE Compare-ObjectsAdvanced -Object1 $Users -Object2 $Groups -CommonProperty 'DistinguishedName' -AddObjectArrayName @('Type', 'Status') -AddObjectArray @('User', 'Active') -Side 'Right' Description: Compares two sets of objects based on the 'DistinguishedName' property, adding 'Type' and 'Status' properties to Object1, and compares from the 'Right' side. #> param( [object] $Object1, [object] $Object2, [alias('Property')][string] $CommonProperty = 'DistinguishedName', [string[]] $AddObjectArrayName, [object[]] $AddObjectArray, [string] $Object1Property, [string] $Object2Property, [string] $ObjectPropertySubstitute = 'SpecialValueToCompare', [switch] $RemoveSideIndicator, [switch] $KeepTemporaryProperty, [ValidateSet('Left', 'Right')][string] $Side = 'Left' # May need Both later on ) $Objects = New-GenericList if ($null -eq $Object1 -and $null -eq $Object2) { } elseif ($null -eq $Object1) { } elseif ($null -eq $Object2) { foreach ($G in $Object1) { for ($a = 0; $a -lt $AddObjectArrayName.Count; $a++) { $G | Add-Member -MemberType NoteProperty -Name $AddObjectArrayName[$a] -Value $AddObjectArray[$a] -Force } $Objects.Add($G) } } else { $Terminate = New-GenericList -Type [bool] if ($Object1Property -and $Object2Property) { if ($Object1[0].PSObject.Properties.Name -notcontains $Object1Property) { Write-Warning -Message "Compare-InfrastructureObjects - Object1 property doesn't exists $Object1Property" $Terminate.Add($true) } if ($Object2[0].PSObject.Properties.Name -notcontains $Object2Property) { Write-Warning -Message "Compare-InfrastructureObjects - Object2 property doesn't exists $Object2Property" $Terminate.Add($true) } if ($Terminate -contains $true) { return } $Object1 | Add-Member -MemberType AliasProperty -Name $ObjectPropertySubstitute -Value $Object1Property -Force $Object2 | Add-Member -MemberType AliasProperty -Name $ObjectPropertySubstitute -Value $Object2Property -Force $Compare = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -Property $ObjectPropertySubstitute -PassThru } else { if ($Object1[0].PSObject.Properties.Name -notcontains $CommonProperty) { Write-Warning -Message "Compare-InfrastructureObjects - Object1 property doesn't exists $CommonProperty" $Terminate.Add($true) } if ($Object2[0].PSObject.Properties.Name -notcontains $CommonProperty) { Write-Warning -Message "Compare-InfrastructureObjects - Object2 property doesn't exists $CommonProperty" $Terminate.Add($true) } if ($Terminate -contains $true) { return } $Compare = Compare-Object -ReferenceObject $Object1 -DifferenceObject $Object2 -Property $CommonProperty -PassThru } if ($Side -eq 'Left') { $Compare = $Compare | Where-Object { $_.SideIndicator -eq '<=' } } elseif ($Side -eq 'Right') { $Compare = $Compare | Where-Object { $_.SideIndicator -eq '=>' } } else { $Compare = $Compare | Where-Object { $_.SideIndicator -eq '==' } } foreach ($G in $Compare) { if ($RemoveSideIndicator) { $G.PSObject.Members.Remove('SideIndicator') } if (-not $KeepTemporaryProperty) { $G.PSObject.Members.Remove($ObjectPropertySubstitute) } for ($a = 0; $a -lt $AddObjectArrayName.Count; $a++) { $G | Add-Member -MemberType NoteProperty -Name $AddObjectArrayName[$a] -Value $AddObjectArray[$a] -Force } $Objects.Add($G) } } return $Objects } Function Compare-ObjectProperties { <# .SYNOPSIS Compares the properties of two objects and returns the differences. .DESCRIPTION This function compares the properties of two objects and returns the differences found between them. It compares each property of the reference object with the corresponding property of the difference object. .PARAMETER ReferenceObject The reference object to compare properties against. .PARAMETER DifferenceObject The object whose properties are compared against the reference object. .PARAMETER CaseSensitive Indicates whether the comparison should be case-sensitive. Default is false. .EXAMPLE $ad1 = Get-ADUser amelia.mitchell -Properties * $ad2 = Get-ADUser carolyn.quinn -Properties * Compare-ObjectProperties $ad1 $ad2 #> Param( [PSObject]$ReferenceObject, [PSObject]$DifferenceObject, [switch]$CaseSensitive = $false ) $objprops = @( $($ReferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name), $($DifferenceObject | Get-Member -MemberType Property, NoteProperty | ForEach-Object Name) ) $objprops = $objprops | Sort-Object -Unique $diffs = foreach ($objprop in $objprops) { $diff = Compare-Object $ReferenceObject $DifferenceObject -Property $objprop -CaseSensitive:$CaseSensitive if ($diff) { $diffprops = [PsCustomObject] @{ PropertyName = $objprop RefValue = ($diff | Where-Object { $_.SideIndicator -eq '<=' } | ForEach-Object $($objprop)) DiffValue = ($diff | Where-Object { $_.SideIndicator -eq '=>' } | ForEach-Object $($objprop)) } $diffprops } } if ($diffs) { return ($diffs | Select-Object PropertyName, RefValue, DiffValue) } } function Copy-Dictionary { <# .SYNOPSIS Copies dictionary/hashtable .DESCRIPTION Copies dictionary uusing PS Serializer. Replaces usage of BinnaryFormatter due to no support in PS 7.4 .PARAMETER Dictionary Dictionary to copy .EXAMPLE $Test = [ordered] @{ Test = 'Test' Test1 = @{ Test2 = 'Test2' Test3 = @{ Test4 = 'Test4' } } Test2 = @( "1", "2", "3" ) Test3 = [PSCustomObject] @{ Test4 = 'Test4' Test5 = 'Test5' } } $New1 = Copy-Dictionary -Dictionary $Test $New1 .NOTES #> [alias('Copy-Hashtable', 'Copy-OrderedHashtable')] [cmdletbinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [System.Management.Automation.PSSerializer]::Serialize($Dictionary, [int32]::MaxValue) return [System.Management.Automation.PSSerializer]::Deserialize($clone) } function Copy-DictionaryManual { <# .SYNOPSIS Copies a dictionary recursively, handling nested dictionaries and lists. .DESCRIPTION This function copies a dictionary recursively, handling nested dictionaries and lists. It creates a deep copy of the input dictionary, ensuring that modifications to the copied dictionary do not affect the original dictionary. .PARAMETER Dictionary The dictionary to be copied. .EXAMPLE $originalDictionary = @{ 'Key1' = 'Value1' 'Key2' = @{ 'NestedKey1' = 'NestedValue1' } } $copiedDictionary = Copy-DictionaryManual -Dictionary $originalDictionary This example demonstrates how to copy a dictionary with nested values. #> [CmdletBinding()] param( [System.Collections.IDictionary] $Dictionary ) $clone = [ordered] @{} foreach ($Key in $Dictionary.Keys) { $value = $Dictionary.$Key $clonedValue = switch ($Dictionary.$Key) { { $null -eq $_ } { $null continue } { $_ -is [System.Collections.IDictionary] } { Copy-DictionaryManual -Dictionary $_ continue } { $type = $_.GetType() $type.IsPrimitive -or $type.IsValueType -or $_ -is [string] } { $_ continue } default { $_ | Select-Object -Property * } } if ($value -is [System.Collections.IList]) { $clone[$Key] = @($clonedValue) } else { $clone[$Key] = $clonedValue } } $clone } function Format-Dictionary { <# .SYNOPSIS Sorts dictionary/hashtable keys including nested hashtables and returns ordered dictionary .DESCRIPTION Sorts dictionary/hashtable keys including nested hashtables and returns ordered dictionary .PARAMETER Hashtable Hashtable to sort .EXAMPLE $Hashtable = [ordered] @{ ModuleVersion = '2.0.X' #PreReleaseTag = 'Preview5' CompatiblePSEditions = @('Desktop', 'Core') RunMe = @{ Name = 'RunMe' Type = 'Script' Path = 'RunMe.ps1' } GUID = 'eb76426a-1992-40a5-82cd-6480f883ef4d' Author = 'Przemyslaw Klys' CompanyName = 'Evotec' Copyright = "(c) 2011 - $((Get-Date).Year) Przemyslaw Klys @ Evotec. All rights reserved." Description = 'Simple project allowing preparing, managing, building and publishing modules to PowerShellGallery' PowerShellVersion = '5.1' Tags = @('Windows', 'MacOS', 'Linux', 'Build', 'Module') IconUri = 'https://evotec.xyz/wp-content/uploads/2019/02/PSPublishModule.png' ProjectUri = 'https://github.com/EvotecIT/PSPublishModule' DotNetFrameworkVersion = '4.5.2' } $Hashtable = Format-Dictionary -Hashtable $Hashtable $Hashtable .EXAMPLE $Hashtable = Format-Dictionary -Hashtable $Hashtable -ByValue $Hashtable .EXAMPLE $Hashtable = Format-Dictionary -Hashtable $Hashtable -ByValue -Descending $Hashtable .NOTES General notes #> [alias('Sort-Dictionary')] [CmdletBinding()] param( [alias('Dictionary', 'OrderedDictionary')] [Parameter(Mandatory, ValueFromPipeline)] [System.Collections.IDictionary] $Hashtable, [switch] $Descending, [switch] $ByValue ) $Ordered = [ordered] @{} if ($ByValue) { $Hashtable.GetEnumerator() | Sort-Object -Property Value -Descending:$Descending.IsPresent | ForEach-Object { if ($_.Value -is [System.Collections.IDictionary]) { $Ordered[$_.Key] = Format-Dictionary -Hashtable $_.Value -Descending:$Descending.IsPresent -ByValue:$ByValue.IsPresent } else { $Ordered[$_.Key] = $_.Value } } } else { foreach ($K in [string[]] $Hashtable.Keys | Sort-Object -Descending:$Descending.IsPresent) { if ($Hashtable[$K] -is [System.Collections.IDictionary]) { $Ordered[$K] = Format-Dictionary -Hashtable $Hashtable[$K] -Descending:$Descending.IsPresent -ByValue:$ByValue.IsPresent } else { $Ordered[$K] = $Hashtable[$K] } } } $Ordered } function Format-FirstXChars { <# .SYNOPSIS This function returns the first X characters of a given text string. .DESCRIPTION The Format-FirstXChars function takes a text string and a number of characters as input and returns the first X characters of the text string. .PARAMETER Text The input text string from which the first X characters will be extracted. .PARAMETER NumberChars The number of characters to extract from the beginning of the input text string. .EXAMPLE Format-FirstXChars -Text "VERBOSE: Loading module from path 'C:\Users\pklys\.vscode\extensions\ms-vs" -NumberChars 15 # Returns: VERBOSE: Loading .NOTES This function is useful for truncating long text strings to a specific length. #> param( [string] $Text, [int] $NumberChars ) return ($Text.ToCharArray() | Select-Object -First $NumberChars) -join '' } function Format-PSTable { <# .SYNOPSIS Formats a collection of objects into a table for display. .DESCRIPTION The Format-PSTable function takes a collection of objects and formats them into a table for easy display. It provides options to customize the output by selecting specific properties, excluding certain properties, and more. .PARAMETER Object Specifies the collection of objects to format. .PARAMETER SkipTitle Indicates whether to skip displaying the title row in the table. .PARAMETER Property Specifies an array of property names to include in the table. .PARAMETER ExcludeProperty Specifies an array of property names to exclude from the table. .PARAMETER OverwriteHeaders Specifies an object to use as headers for the table. .PARAMETER PreScanHeaders Indicates whether to pre-scan the object properties to determine headers. .PARAMETER Splitter Specifies the delimiter to use when joining array values. .EXAMPLE $data | Format-PSTable Description: Formats the $data collection into a table with default settings. .EXAMPLE $data | Format-PSTable -Property Name, Age Description: Formats the $data collection into a table displaying only the 'Name' and 'Age' properties. .EXAMPLE $data | Format-PSTable -ExcludeProperty ID Description: Formats the $data collection into a table excluding the 'ID' property. #> [CmdletBinding()] param ( [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][System.Collections.ICollection] $Object, [switch] $SkipTitle, [string[]] $Property, [string[]] $ExcludeProperty, [Object] $OverwriteHeaders, [switch] $PreScanHeaders, [string] $Splitter = ';' ) if ($Object[0] -is [System.Collections.IDictionary]) { $Array = @( if (-not $SkipTitle) { , @('Name', 'Value') } foreach ($O in $Object) { foreach ($Name in $O.Keys) { $Value = $O[$Name] if ($O[$Name].Count -gt 1) { $Value = $O[$Name] -join $Splitter } else { $Value = $O[$Name] } , @($Name, $Value) } } ) if ($Array.Count -eq 1) { , $Array } else { $Array } } elseif ($Object[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { return $Object } else { if ($Property) { $Object = $Object | Select-Object -Property $Property } $Array = @( if ($PreScanHeaders) { $Titles = Get-ObjectProperties -Object $Object } elseif ($OverwriteHeaders) { $Titles = $OverwriteHeaders } else { $Titles = $Object[0].PSObject.Properties.Name } if (-not $SkipTitle) { , $Titles } foreach ($O in $Object) { $ArrayValues = foreach ($Name in $Titles) { $Value = $O."$Name" if ($Value.Count -gt 1) { $Value -join $Splitter } elseif ($Value.Count -eq 1) { if ($Value.Value) { $Value.Value } else { $Value } } else { '' } } , $ArrayValues } ) if ($Array.Count -eq 1) { , $Array } else { $Array } } } function Format-Stream { <# .SYNOPSIS Formats input objects for display in a stream-oriented manner. .DESCRIPTION The Format-Stream function formats input objects for display in a stream-oriented manner. It provides flexibility in displaying data in various streams such as Output, Host, Warning, Verbose, Debug, and Information. .PARAMETER InputObject Specifies the input objects to be formatted. .PARAMETER Property Specifies the properties of the input objects to include in the output. .PARAMETER ExcludeProperty Specifies the properties of the input objects to exclude from the output. .PARAMETER HideTableHeaders Indicates whether to hide the table headers in the output. .PARAMETER ColumnHeaderSize Specifies the size of the column headers in the output. .PARAMETER AlignRight Indicates whether to align the output data to the right. .PARAMETER Stream Specifies the stream to display the formatted data. Valid values are 'Output', 'Host', 'Warning', 'Verbose', 'Debug', and 'Information'. .PARAMETER List Indicates whether to display the output as a list. .PARAMETER Transpose Indicates whether to transpose the columns and rows of the output. .PARAMETER TransposeSort Specifies the sorting order when transposing the data. Valid values are 'ASC', 'DESC', and 'NONE'. .PARAMETER ForegroundColor Specifies the foreground color of the output. .PARAMETER ForegroundColorRow Specifies the foreground color of specific rows in the output. .EXAMPLE Get-Process | Format-Stream -Property Name, Id -Stream Host Displays the Name and Id properties of the processes in the Host stream. .EXAMPLE Get-Service | Format-Stream -ExcludeProperty Status -List Displays all service properties except Status as a list. #> [alias('FS', 'Format-TableStream', 'Format-ListStream')] ##[alias('ftv','ftd','fto','fth','fti','flv','fld','flo','flh','fli','Format-TableVerbose', 'Format-TableDebug', 'Format-TableInformation', 'Format-TableWarning')] [CmdletBinding(DefaultParameterSetName = 'All')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1)] [Array] $InputObject, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 0, ParameterSetName = 'Property')] [string[]] $Property, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 2, ParameterSetName = 'ExcludeProperty')] [string[]] $ExcludeProperty, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)] [switch] $HideTableHeaders, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 4)] [int] $ColumnHeaderSize, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 5)] [switch] $AlignRight, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6)] [validateset('Output', 'Host', 'Warning', 'Verbose', 'Debug', 'Information')] [string] $Stream = 'Verbose', [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 7)] [alias('AsList')][switch] $List, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 8)] [alias('Rotate', 'RotateData', 'TransposeColumnsRows', 'TransposeData')] [switch] $Transpose, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 9)] [ValidateSet('ASC', 'DESC', 'NONE')] [string] $TransposeSort = 'NONE', [alias('Color')] [System.ConsoleColor[]] $ForegroundColor, [alias('ColorRow')] [int[]] $ForegroundColorRow ) Begin { $IsVerbosePresent = $PSCmdlet.MyInvocation.BoundParameters['Verbose'].IsPresent if ($Stream -eq 'Output') { } elseif ($Stream -eq 'Host') { } elseif ($Stream -eq 'Warning') { [System.Management.Automation.ActionPreference] $WarningCurrent = $WarningPreference $WarningPreference = 'continue' } elseif ($Stream -eq 'Verbose') { [System.Management.Automation.ActionPreference] $VerboseCurrent = $VerbosePreference $VerbosePreference = 'continue' } elseif ($Stream -eq 'Debug') { [System.Management.Automation.ActionPreference] $DebugCurrent = $DebugPreference $DebugPreference = 'continue' } elseif ($Stream -eq 'Information') { [System.Management.Automation.ActionPreference] $InformationCurrent = $InformationPreference $InformationPreference = 'continue' } [bool] $FirstRun = $True [bool] $FirstLoop = $True [bool] $FirstList = $True [int] $ScreenWidth = $Host.UI.RawUI.WindowSize.Width - 12 $ArrayList = @() } Process { if ($InputObject.Count -eq 0) { break } if ($FirstRun) { $FirstRun = $false if ($Transpose) { $InputObject = Format-TransposeTable -Object $InputObject -Sort $TransposeSort } $Data = Format-PSTable -Object $InputObject -Property $Property -ExcludeProperty $ExcludeProperty -NoAliasOrScriptProperties:$NoAliasOrScriptProperties -DisplayPropertySet:$DisplayPropertySet -PreScanHeaders:$PreScanHeaders $Headers = $Data[0] if ($HideTableHeaders) { $Data.RemoveAt(0); } $ArrayList += $Data } else { if ($Transpose) { $InputObject = Format-TransposeTable -Object $InputObject -Sort $TransposeSort } $Data = Format-PSTable -Object $InputObject -Property $Property -ExcludeProperty $ExcludeProperty -NoAliasOrScriptProperties:$NoAliasOrScriptProperties -DisplayPropertySet:$DisplayPropertySet -PreScanHeaders:$PreScanHeaders -OverwriteHeaders $Headers -SkipTitle $ArrayList += $Data } } End { if (-not $ColumnHeaderSize) { $ColumnLength = [int[]]::new($Headers.Count); foreach ($Row in $ArrayList) { $i = 0 foreach ($Column in $Row) { $Length = "$Column".Length if ($Length -gt $ColumnLength[$i]) { $ColumnLength[$i] = $Length } $i++ } } if ($IsVerbosePresent) { Write-Verbose "Format-TableVerbose - ScreenWidth $ScreenWidth" Write-Verbose "Format-TableVerbose - Column Lengths $($ColumnLength -join ',')" } } if ($Stream -eq 'Output') { Write-Output -InputObject '' } elseif ($Stream -eq 'Host') { Write-Host -Object '' } elseif ($Stream -eq 'Warning') { Write-Warning -Message '' } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message '' } elseif ($Stream -eq 'Debug') { Write-Debug -Message '' } elseif ($Stream -eq 'Information') { Write-Information -MessageData '' } if ($List) { [int] $RowCount = 1 foreach ($Row in $ArrayList ) { [string] $Output = '' [int] $ColumnNumber = 0 [int] $CurrentColumnLength = 0 if ($ColumnHeaderSize) { $PadLength = $ColumnHeaderSize } else { $PadLength = (($Headers.Length | Measure-Object -Maximum).Maximum) + 1 } if (-not $FirstList) { $i = 0 foreach ($ColumnValue in $Row) { if (-not $HideTableHeaders) { if ($AlignRight) { $Head = $($Headers[$i]).PadLeft($PadLength) } else { $Head = $($Headers[$i]).PadRight($PadLength) } $Output = "$Head`: $ColumnValue" } else { $Output = "$ColumnValue" } if ($Stream -eq 'Output') { Write-Output -InputObject $Output } elseif ($Stream -eq 'Host') { Write-Host -Object $Output } elseif ($Stream -eq 'Warning') { Write-Warning -Message $Output } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message $Output } elseif ($Stream -eq 'Debug') { Write-Debug -Message $Output } elseif ($Stream -eq 'Information') { Write-Information -MessageData $Output } $i++ } $RowCount++ if ($RowCount -ne $ArrayList.Count) { if ($Stream -eq 'Output') { Write-Output -InputObject '' } elseif ($Stream -eq 'Host') { Write-Host -Object '' } elseif ($Stream -eq 'Warning') { Write-Warning -Message '' } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message '' } elseif ($Stream -eq 'Debug') { Write-Debug -Message '' } elseif ($Stream -eq 'Information') { Write-Information -MessageData '' } } } $FirstList = $false } } else { [int] $RowCountColors = 1 foreach ($Row in $ArrayList ) { [string] $Output = '' [int] $ColumnNumber = 0 [int] $CurrentColumnLength = 0 foreach ($ColumnValue in $Row) { if ($ColumnHeaderSize) { $PadLength = $ColumnHeaderSize } else { $PadLength = $ColumnLength[$ColumnNumber] + 1 } $CurrentColumnLength += $PadLength if ($CurrentColumnLength -ge $ScreenWidth) { break } if ($ColumnHeaderSize) { $ColumnValue = ("$ColumnValue".ToCharArray() | Select-Object -First ($PadLength - 1)) -join '' } else { $ColumnValue = ("$ColumnValue".ToCharArray() | Select-Object -First ($PadLength)) -join '' } if ($Output -eq '') { if ($AlignRight) { $Output = "$ColumnValue".PadLeft($PadLength) } else { $Output = "$ColumnValue".PadRight($PadLength) } } else { if ($AlignRight) { $Output = $Output + "$ColumnValue".PadLeft($PadLength) } else { $Output = $Output + "$ColumnValue".PadRight($PadLength) } } $ColumnNumber++ } if ($Stream -eq 'Output') { Write-Output -InputObject $Output } elseif ($Stream -eq 'Host') { if ($ForegroundColorRow -contains $RowCountColors) { [int] $Index = $ForegroundColorRow.IndexOf($RowCountColors) Write-Host -Object $Output -ForegroundColor $ForegroundColor[$Index] } else { Write-Host -Object $Output } } elseif ($Stream -eq 'Warning') { Write-Warning -Message $Output } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message $Output } elseif ($Stream -eq 'Debug') { Write-Debug -Message $Output } elseif ($Stream -eq 'Information') { Write-Information -MessageData $Output } if (-not $HideTableHeaders) { if ($FirstLoop) { $HeaderUnderline = $Output -Replace '\w', '-' if ($Stream -eq 'Output') { Write-Output -InputObject $HeaderUnderline } elseif ($Stream -eq 'Host') { if ($ForegroundColorRow -contains $RowCountColors) { [int] $Index = $ForegroundColorRow.IndexOf($RowCountColors) Write-Host -Object $HeaderUnderline -ForegroundColor $ForegroundColor[$Index] } else { Write-Host -Object $HeaderUnderline } } elseif ($Stream -eq 'Warning') { Write-Warning -Message $HeaderUnderline } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message $HeaderUnderline } elseif ($Stream -eq 'Debug') { Write-Debug -Message $HeaderUnderline } elseif ($Stream -eq 'Information') { Write-Information -MessageData $HeaderUnderline } } } $FirstLoop = $false $RowCountColors++ } } if ($Stream -eq 'Output') { Write-Output -InputObject '' } elseif ($Stream -eq 'Host') { Write-Host -Object '' } elseif ($Stream -eq 'Warning') { Write-Warning -Message '' } elseif ($Stream -eq 'Verbose') { Write-Verbose -Message '' } elseif ($Stream -eq 'Debug') { Write-Debug -Message '' } elseif ($Stream -eq 'Information') { Write-Information -MessageData '' } if ($Stream -eq 'Output') { } elseif ($Stream -eq 'Host') { } elseif ($Stream -eq 'Warning') { $WarningPreference = $WarningCurrent } elseif ($Stream -eq 'Verbose') { $VerbosePreference = $VerboseCurrent } elseif ($Stream -eq 'Debug') { $DebugPreference = $DebugCurrent } elseif ($Stream -eq 'Information') { $InformationPreference = $InformationCurrent } } } function Format-StringToSentence { <# .SYNOPSIS Formats a given string by adding spaces before uppercase letters, digits, and non-word characters. .DESCRIPTION The Format-AddSpaceToSentence function takes a string or an array of strings and adds a space before each uppercase letter, digit, and non-word character (excluding dots, spaces, and underscores). It also provides options to convert the string to lowercase, remove certain characters before or after the formatting, and remove double spaces. .PARAMETER Text The string or array of strings to be formatted. .PARAMETER RemoveCharsBefore An array of characters to be removed from the string before the formatting is applied. .PARAMETER RemoveCharsAfter An array of characters to be removed from the string after the formatting is applied. .PARAMETER ToLowerCase If this switch is present, the function will convert the string to lowercase. .PARAMETER RemoveDoubleSpaces If this switch is present, the function will remove any double spaces from the string. .PARAMETER MakeWordsUpperCase An array of words that should be converted to uppercase after the formatting is applied. .PARAMETER DisableAddingSpace If this switch is present, the function will not add spaces before uppercase letters, digits, and non-word characters. .EXAMPLE $test = @( 'OnceUponATime', 'OnceUponATime1', 'Money@Risk', 'OnceUponATime123', 'AHappyMan2014' 'OnceUponATime_123' 'Domain test.com' ) Format-StringToSentence -Text $Test -RemoveCharsAfter '_' -RemoveDoubleSpaces This example formats each string in the $test array, removes any underscores after the formatting, and removes any double spaces. .EXAMPLE $test = @( 'OnceUponATime', 'OnceUponATime1', 'Money@Risk', 'OnceUponATime123', 'AHappyMan2014' 'OnceUponATime_123' 'Domain test.com' ) $Test | Format-StringToSentence -ToLowerCase -RemoveCharsAfter '_' -RemoveDoubleSpaces This example does the same as the previous one, but also converts each string to lowercase. .EXAMPLE $test = @( 'OnceUponATime', 'OnceUponATime1', 'Money@Risk', 'OnceUponATime123', 'AHappyMan2014' 'OnceUponATime_123' 'Domain test.com' ) Format-StringToSentence -Text $Test -RemoveCharsAfter '_' -RemoveDoubleSpaces -MakeWordsUpperCase 'test.com', 'money' .NOTES The function uses the -creplace operator to add spaces, which is case-insensitive. Therefore, it will add spaces before both uppercase and lowercase letters if they are specified in the RemoveCharsBefore or RemoveCharsAfter parameters. #> [alias('Format-AddSpaceToSentence')] [CmdletBinding()] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)][string[]] $Text, [string[]] $RemoveCharsBefore, [string[]] $RemoveCharsAfter, [switch] $ToLowerCase, [switch] $RemoveDoubleSpaces, [string[]] $MakeWordsUpperCase, [switch] $DisableAddingSpace ) Process { foreach ($T in $Text) { if ($RemoveCharsBefore) { foreach ($R in $RemoveCharsBefore) { $T = $T -ireplace [regex]::Escape($R), "" } } if (-not $DisableAddingSpace) { $T = $T -creplace '([A-Z]|[^a-zA-Z0-9_.\s]|_|\d+)(?<![a-z])', ' $&' } if ($ToLowerCase) { $T = $T.ToLower() } if ($RemoveCharsAfter) { foreach ($R in $RemoveCharsAfter) { $T = $T -ireplace [regex]::Escape($R), "" } } if ($RemoveDoubleSpaces) { $T = $T -creplace '\s+', ' ' } if ($MakeWordsUpperCase) { foreach ($W in $MakeWordsUpperCase) { $T = $T -ireplace [regex]::Escape($W), $W.ToUpper() } } $T.Trim() } } } function Format-ToTitleCase { <# .SYNOPSIS Formats string or number of strings to Title Case .DESCRIPTION Formats string or number of strings to Title Case allowing for prettty display .PARAMETER Text Sentence or multiple sentences to format .PARAMETER RemoveWhiteSpace Removes spaces after formatting string to Title Case. .PARAMETER RemoveChar Array of characters to remove .EXAMPLE Format-ToTitleCase 'me' Output: Me .EXAMPLE 'me i feel good' | Format-ToTitleCase Output: Me I Feel Good Not Feel .EXAMPLE 'me i feel', 'not feel' | Format-ToTitleCase Output: Me I Feel Good Not Feel .EXAMPLE Format-ToTitleCase -Text 'This is my thing' -RemoveWhiteSpace Output: ThisIsMyThing .EXAMPLE Format-ToTitleCase -Text "This is my thing: That - No I don't want all chars" -RemoveWhiteSpace -RemoveChar ',', '-', "'", '\(', '\)', ':' .NOTES General notes #> [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)][string[]] $Text, [switch] $RemoveWhiteSpace, [string[]] $RemoveChar ) Begin { } Process { $Conversion = foreach ($T in $Text) { $Output = (Get-Culture).TextInfo.ToTitleCase($T) foreach ($Char in $RemoveChar) { $Output = $Output -replace $Char } if ($RemoveWhiteSpace) { $Output = $Output -replace ' ', '' } $Output } $Conversion } End { } } function Format-TransposeTable { <# .SYNOPSIS Transposes (pivot) a table of objects .DESCRIPTION Transposes (pivot) a table of objects .PARAMETER AllObjects List of objects to transpose .PARAMETER Sort Legacy parameter to sort the output .PARAMETER Legacy Allows to transpose the table in a legacy way .PARAMETER Property Provides a property to name the column based on the property value .EXAMPLE $T = [PSCustomObject] @{ Name = "Server 1" Test = 1 Test2 = 7 Ole = 'bole' Trolle = 'A' Alle = 'sd' } $T1 = [PSCustomObject] @{ Name = "Server 2" Test = 2 Test2 = 3 Ole = '1bole' Trolle = 'A' Alle = 'sd' } Format-TransposeTable -Object @($T, $T1) -Property "Name" | Format-Table .EXAMPLE $T2 = [ordered] @{ Name = "Server 1" Test = 1 Test2 = 7 Ole = 'bole' Trolle = 'A' Alle = 'sd' } $T3 = [ordered] @{ Name = "Server 2" Test = 2 Test2 = 3 Ole = '1bole' Trolle = 'A' Alle = 'sd' } $Test = Format-TransposeTable -Object @($T2, $T3) $Test | Format-Table .NOTES General notes #> [CmdletBinding(DefaultParameterSetName = 'Pivot')] param ( [Parameter(ParameterSetName = 'Legacy')] [Parameter(ParameterSetName = 'Pivot')] [Alias("Object")] [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)][System.Collections.ICollection] $AllObjects, [Parameter(ParameterSetName = 'Legacy')] [ValidateSet("ASC", "DESC", "NONE")][String] $Sort = 'NONE', [Parameter(ParameterSetName = 'Legacy')] [switch] $Legacy, [Parameter(ParameterSetName = 'Pivot')] [string] $Property, [Parameter(ParameterSetName = 'Pivot')] [string] $Name = "Object " ) begin { $Object = [System.Collections.Generic.List[object]]::new() } process { foreach ($O in $AllObjects) { $Object.Add($O) } } end { if (-not $Legacy) { if ($Object[0] -is [System.Collections.IDictionary]) { $ListHeader = [System.Collections.Generic.List[string]]::new() $ListHeader.Add('Name') if ($Property) { foreach ($myObject in $Object) { $ListHeader.Add($myObject.$Property) } } else { for ($i = 0; $i -lt $Object.Count; $i++) { $ListHeader.Add("$($Name)$i") } } $CountOfProperties = $Object[0].GetEnumerator().Name.Count [Array] $ObjectsList = for ($i = 0; $i -lt $CountOfProperties; $i++) { $TranslatedObject = [ordered] @{ 'Name' = $Object[0].GetEnumerator().Name[$i] } foreach ($Header in $ListHeader) { if ($Header -ne 'Name') { $TranslatedObject[$Header] = '' } } $TranslatedObject } for ($i = 0; $i -lt $ObjectsList.Count; $i++) { for ($j = 0; $j -lt $Object.Count; $j++) { $NameOfProperty = $ObjectsList[$i].Name $ObjectsList[$i][$j + 1] = $Object[$j].$NameOfProperty } [PSCustomObject] $ObjectsList[$i] } } else { $ListHeader = [System.Collections.Generic.List[string]]::new() $ListHeader.Add('Name') if ($Property) { foreach ($myObject in $Object) { $ListHeader.Add($myObject.$Property) } } else { for ($i = 0; $i -lt $Object.Count; $i++) { $ListHeader.Add("$($Name)$i") } } $CountOfProperties = $Object[0].PSObject.Properties.Name.Count [Array] $ObjectsList = for ($i = 0; $i -lt $CountOfProperties; $i++) { $TranslatedObject = [ordered] @{ 'Name' = $Object[0].PSObject.Properties.Name[$i] } foreach ($Header in $ListHeader) { if ($Header -ne 'Name') { $TranslatedObject[$Header] = '' } } $TranslatedObject } for ($i = 0; $i -lt $ObjectsList.Count; $i++) { for ($j = 0; $j -lt $Object.Count; $j++) { $NameOfProperty = $ObjectsList[$i].Name $ObjectsList[$i][$j + 1] = $Object[$j].$NameOfProperty } [PSCustomObject] $ObjectsList[$i] } } } else { foreach ($myObject in $Object) { if ($myObject -is [System.Collections.IDictionary]) { if ($Sort -eq 'ASC') { [PSCustomObject] $myObject.GetEnumerator() | Sort-Object -Property Name -Descending:$false } elseif ($Sort -eq 'DESC') { [PSCustomObject] $myObject.GetEnumerator() | Sort-Object -Property Name -Descending:$true } else { [PSCustomObject] $myObject } } else { $Output = [ordered] @{ } if ($Sort -eq 'ASC') { $myObject.PSObject.Properties | Sort-Object -Property Name -Descending:$false | ForEach-Object { $Output["$($_.Name)"] = $_.Value } } elseif ($Sort -eq 'DESC') { $myObject.PSObject.Properties | Sort-Object -Property Name -Descending:$true | ForEach-Object { $Output["$($_.Name)"] = $_.Value } } else { $myObject.PSObject.Properties | ForEach-Object { $Output["$($_.Name)"] = $_.Value } } $Output } } } } } function Format-View { <# .SYNOPSIS Formats and displays objects in a customizable view. .DESCRIPTION The Format-View function formats and displays objects in a customizable view. It allows you to specify properties to include or exclude, control output streams, and customize the display format. .PARAMETER InputObject Specifies the object to format. .PARAMETER Property Specifies the properties of the object to include in the output. .PARAMETER ExcludeProperty Specifies the properties of the object to exclude from the output. .PARAMETER HideTableHeaders Indicates whether to hide table headers in the output. .PARAMETER Stream Specifies the output stream for the formatted object. Valid values are 'Output', 'Host', 'Warning', 'Verbose', 'Debug', and 'Information'. .PARAMETER List Indicates whether to display the object as a list. .PARAMETER Autosize Indicates whether to automatically adjust the column width based on the content. .EXAMPLE Format-View -InputObject $object -Property Name, Age -Stream Verbose Formats the object with only the 'Name' and 'Age' properties and outputs to the verbose stream. .EXAMPLE Get-Process | Format-View -Property Name, CPU -Stream Host Formats the process objects with only the 'Name' and 'CPU' properties and outputs to the host. #> [alias('FV', 'Format-Verbose', 'Format-Debug', 'Format-Warning')] [CmdletBinding(DefaultParameterSetName = 'All')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)] [object] $InputObject, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 1, ParameterSetName = 'Property')] [Object[]] $Property, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 2, ParameterSetName = 'ExcludeProperty')] [Object[]] $ExcludeProperty, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)] [switch] $HideTableHeaders, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6)] [validateset('Output', 'Host', 'Warning', 'Verbose', 'Debug', 'Information')] [string] $Stream = 'Verbose', [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 7)] [alias('AsList')][switch] $List, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 8)] [switch] $Autosize ) Begin { $IsVerbosePresent = $PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent if ($Stream -eq 'Output') { } elseif ($Stream -eq 'Host') { } elseif ($Stream -eq 'Warning') { [System.Management.Automation.ActionPreference] $WarningCurrent = $WarningPreference $WarningPreference = 'continue' } elseif ($Stream -eq 'Verbose') { [System.Management.Automation.ActionPreference] $VerboseCurrent = $VerbosePreference $VerbosePreference = 'continue' } elseif ($Stream -eq 'Debug') { [System.Management.Automation.ActionPreference] $DebugCurrent = $DebugPreference $DebugPreference = 'continue' } elseif ($Stream -eq 'Information') { [System.Management.Automation.ActionPreference] $InformationCurrent = $InformationPreference $InformationPreference = 'continue' } [bool] $FirstRun = $True [bool] $FirstLoop = $True [bool] $FirstList = $True [int] $ScreenWidth = $Host.UI.RawUI.WindowSize.Width - 12 $MyList = [System.Collections.Generic.List[Object]]::new() } Process { $MyList.Add($InputObject) } End { $Parameters = @{} if ($Property) { $Parameters.Property = $Property } if ($ExcludeProperty) { $Parameters.ExcludeProperty = $ExcludeProperty } if ($HideTableHeaders) { $Parameters.HideTableHeaders = $HideTableHeaders } if ($List) { if ($Stream -eq 'Output') { $MyList | Format-List @Parameters | Out-String | Write-Output } elseif ($Stream -eq 'Host') { $MyList | Format-List @Parameters | Out-String | Write-Host } elseif ($Stream -eq 'Warning') { $MyList | Format-List @Parameters | Out-String | Write-Warning $WarningPreference = $WarningCurrent } elseif ($Stream -eq 'Verbose') { $MyList | Format-List @Parameters | Out-String | Write-Verbose $VerbosePreference = $VerboseCurrent } elseif ($Stream -eq 'Debug') { $MyList | Format-List @Parameters | Out-String | Write-Debug $DebugPreference = $DebugCurrent } elseif ($Stream -eq 'Information') { $MyList | Format-List @Parameters | Out-String | Write-Information $InformationPreference = $InformationCurrent } } else { if ($Stream -eq 'Output') { $MyList | Format-Table @Parameters | Out-String | Write-Output } elseif ($Stream -eq 'Host') { $MyList | Format-Table @Parameters | Out-String | Write-Host } elseif ($Stream -eq 'Warning') { $MyList | Format-Table @Parameters | Out-String | Write-Warning $WarningPreference = $WarningCurrent } elseif ($Stream -eq 'Verbose') { $MyList | Format-Table @Parameters | Out-String | Write-Verbose $VerbosePreference = $VerboseCurrent } elseif ($Stream -eq 'Debug') { $MyList | Format-Table @Parameters | Out-String | Write-Debug $DebugPreference = $DebugCurrent } elseif ($Stream -eq 'Information') { $MyList | Format-Table @Parameters | Out-String | Write-Information $InformationPreference = $InformationCurrent } } } } function Get-Colors { <# .SYNOPSIS Retrieves RGB color values based on the provided color names. .DESCRIPTION The Get-Colors function retrieves RGB color values from a predefined list based on the color names provided as input. If no color names are specified, it returns all available RGB color values. .PARAMETER Color Specifies an array of color names for which RGB values are to be retrieved. .EXAMPLE Get-Colors -Color "Red", "Green" Retrieves the RGB values for the colors Red and Green. .EXAMPLE Get-Colors Retrieves all available RGB color values. #> [CmdletBinding()] param( [string[]] $Color ) if ($Color) { foreach ($_ in $Color) { $Script:RGBColors.$_ } } else { return $Script:RGBColors } } $ScriptBlockColors = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $Script:RGBColors.Keys | Where-Object { $_ -like "*$wordToComplete*" } } Register-ArgumentCompleter -CommandName Get-Colors -ParameterName Color -ScriptBlock $ScriptBlockColors function Get-DataInformation { <# .SYNOPSIS Retrieves data information based on specified criteria. .DESCRIPTION This function retrieves data information based on the specified criteria. It checks for required types, availability of commands, and executes content if provided. .PARAMETER Content The script block to execute for gathering data. .PARAMETER Text The text message to display when gathering data. .PARAMETER TypesRequired An array of types required for data gathering. .PARAMETER TypesNeeded An array of types needed for data gathering. .PARAMETER Commands An array of commands to check for availability. .PARAMETER SkipAvailability Switch to skip availability check for commands. .EXAMPLE Get-DataInformation -Content { Get-Process } -Text "Gathering process information" -TypesRequired @("System.Diagnostics.Process") -TypesNeeded @("System.Diagnostics.Process") -Commands @("Get-Process") Description: Retrieves process information using the Get-Process command. .EXAMPLE Get-DataInformation -Content { Get-Service } -Text "Gathering service information" -TypesRequired @("System.ServiceProcess.ServiceController") -TypesNeeded @("System.ServiceProcess.ServiceController") -Commands @("Get-Service") Description: Retrieves service information using the Get-Service command. #> [CmdletBinding()] param( [ScriptBlock] $Content, [string] $Text, [Array] $TypesRequired, [Array] $TypesNeeded, [Array] $Commands, [switch] $SkipAvailability ) if (Find-TypesNeeded -TypesRequired $TypesRequired -TypesNeeded $TypesNeeded) { Write-Verbose -Message $Text $Time = Start-TimeLog if ($Commands.Count -gt 0 -and -not $SkipAvailability) { $Available = Test-AvailabilityCommands -Commands $Commands if ($Available -contains $false) { $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Warning "Get-DataInformation - Commands $($Commands -join ", ") is/are not available. Data gathering skipped." Write-Verbose "$Text - Time: $EndTime" return } } if ($null -ne $Content) { & $Content } $EndTime = Stop-TimeLog -Time $Time -Option OneLiner Write-Verbose "$Text - Time: $EndTime" } } function Get-HashMaxValue { <# .SYNOPSIS Gets the maximum value from a hashtable. .DESCRIPTION This function retrieves the maximum value from a given hashtable. It can also return the minimum value if the -Lowest switch is used. .PARAMETER hashTable The hashtable from which to find the maximum value. .PARAMETER Lowest If specified, the function will return the minimum value instead of the maximum. .EXAMPLE $myHashTable = @{ 'A' = 10; 'B' = 20; 'C' = 5 } Get-HashMaxValue -hashTable $myHashTable # Output: 20 .EXAMPLE $myHashTable = @{ 'A' = 10; 'B' = 20; 'C' = 5 } Get-HashMaxValue -hashTable $myHashTable -Lowest # Output: 5 #> [CmdletBinding()] param ( [Object] $hashTable, [switch] $Lowest ) if ($Lowest) { return ($hashTable.GetEnumerator() | Sort-Object value -Descending | Select-Object -Last 1).Value } else { return ($hashTable.GetEnumerator() | Sort-Object value -Descending | Select-Object -First 1).Value } } function Get-MimeType { <# .SYNOPSIS Get-MimeType function returns the MIME type of a file based on its extension. .DESCRIPTION This function takes a file name as input and returns the corresponding MIME type based on the file extension. .PARAMETER FileName Specifies the name of the file for which the MIME type needs to be determined. .EXAMPLE Get-MimeType -FileName "example.jpg" Returns "image/jpeg" as the MIME type for the file "example.jpg". .EXAMPLE Get-MimeType -FileName "example.png" Returns "image/png" as the MIME type for the file "example.png". #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $FileName ) $MimeMappings = @{ '.jpeg' = 'image/jpeg' '.jpg' = 'image/jpeg' '.png' = 'image/png' } $Extension = [System.IO.Path]::GetExtension( $FileName ) $ContentType = $MimeMappings[ $Extension ] if ([string]::IsNullOrEmpty($ContentType)) { return New-Object System.Net.Mime.ContentType } else { return New-Object System.Net.Mime.ContentType($ContentType) } } function Get-ObjectCount { <# .SYNOPSIS Counts the number of objects passed as input. .DESCRIPTION This function calculates and returns the total count of objects passed as input. It is designed to be used in scenarios where counting the number of objects is required. .PARAMETER Object Specifies the object or objects for which the count needs to be calculated. .EXAMPLE Get-Process | Get-ObjectCount Returns the total count of processes currently running. .EXAMPLE $Files = Get-ChildItem -Path "C:\Files" $FileCount = $Files | Get-ObjectCount Returns the total count of files in the specified directory. #> [CmdletBinding()] param( [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)][Object]$Object ) return $($Object | Measure-Object).Count } function Get-ObjectData { <# .SYNOPSIS Retrieves data from an object based on the specified title. .DESCRIPTION This function retrieves data from the specified object based on the provided title. It returns an array of values associated with the title. .PARAMETER Object The object from which data will be retrieved. .PARAMETER Title The title of the data to retrieve from the object. .PARAMETER DoNotAddTitles Switch parameter to indicate whether titles should be included in the output. .EXAMPLE Get-ObjectData -Object $myObject -Title "Name" Retrieves the names associated with the object $myObject. .EXAMPLE Get-ObjectData -Object $myObject -Title "Age" -DoNotAddTitles Retrieves the ages associated with the object $myObject without including the title. #> [CmdletBinding()] param( $Object, $Title, [switch] $DoNotAddTitles ) [Array] $Values = $Object.$Title [Array] $ArrayList = @( if ($Values.Count -eq 1 -and $DoNotAddTitles -eq $false) { "$Title - $($Values[0])" } else { if ($DoNotAddTitles -eq $false) { $Title } foreach ($Value in $Values) { "$Value" } } ) return $ArrayList } Function Get-ObjectEnumValues { <# .SYNOPSIS Retrieves the values of an enumeration type and returns them as a hashtable. .DESCRIPTION This function takes an enumeration type as input and retrieves all its values, storing them in a hashtable where the key is the name of the enum value and the value is the corresponding numeric value. .PARAMETER enum Specifies the enumeration type for which values need to be retrieved. .EXAMPLE Get-ObjectEnumValues -enum [System.DayOfWeek] Retrieves all values of the System.DayOfWeek enumeration and returns them as a hashtable. .EXAMPLE Get-ObjectEnumValues -enum [System.ConsoleColor] Retrieves all values of the System.ConsoleColor enumeration and returns them as a hashtable. #> param( [string]$enum ) $enumValues = @{} [enum]::getvalues([type]$enum) | ForEach-Object { $enumValues.add($_, $_.value__) } $enumValues } function Get-ObjectKeys { <# .SYNOPSIS Retrieves the keys of an object excluding a specified key. .DESCRIPTION This function retrieves the keys of an object while excluding a specified key. It returns an array of keys from the object. .PARAMETER Object The object from which keys need to be retrieved. .PARAMETER Ignore The key to be excluded from the result. .EXAMPLE $object = @{ 'key1' = 'value1'; 'key2' = 'value2'; 'key3' = 'value3' } Get-ObjectKeys -Object $object -Ignore 'key2' # Returns 'key1', 'key3' #> param( [object] $Object, [string] $Ignore ) $Data = $Object.Keys | Where-Object { $_ -notcontains $Ignore } return $Data } function Get-ObjectProperties { <# .SYNOPSIS Retrieves all properties of an object and allows for adding custom properties. .DESCRIPTION This function retrieves all properties of an object provided as input. It also allows for adding custom properties to the list. The function can be useful for ensuring that all properties are known at runtime when exporting to SQL, Excel, or Word. .PARAMETER Object Specifies the object for which properties need to be retrieved. .PARAMETER AddProperties Specifies an array of custom properties to be added to the list. .PARAMETER Sort Indicates whether the properties should be sorted. .PARAMETER RequireUnique Specifies whether the list of properties should be unique. .EXAMPLE $Test = Get-Process Get-ObjectProperties -Object $Test Description ----------- Retrieves all properties of the Get-Process object. .EXAMPLE $Test = Get-Process Get-ObjectProperties -Object $Test -AddProperties 'CustomProperty1', 'CustomProperty2' -Sort -RequireUnique $false Description ----------- Retrieves all properties of the Get-Process object and adds custom properties 'CustomProperty1' and 'CustomProperty2' to the list. The properties are sorted and duplicates are allowed. #> [CmdletBinding()] param ( [System.Collections.ICollection] $Object, [string[]] $AddProperties, # provides ability to add some custom properties [switch] $Sort, [bool] $RequireUnique = $true ) $Properties = @( foreach ($O in $Object) { $ObjectProperties = $O.PSObject.Properties.Name $ObjectProperties } foreach ($Property in $AddProperties) { $Property } ) if ($Sort) { return $Properties | Sort-Object -Unique:$RequireUnique } else { return $Properties | Select-Object -Unique:$RequireUnique } } function Get-ObjectPropertiesAdvanced { <# .SYNOPSIS Retrieves properties of objects and provides the ability to add custom properties. .DESCRIPTION This function retrieves properties of objects and allows users to add custom properties to the output. It calculates the highest count of properties among the objects and returns the properties in an array. .PARAMETER Object Specifies the object or objects whose properties need to be retrieved. .PARAMETER AddProperties Specifies an array of custom properties to be added to the output. .PARAMETER Sort Indicates whether the properties should be sorted alphabetically. .EXAMPLE $objects = Get-ObjectPropertiesAdvanced -Object $myObject -AddProperties @("CustomProperty1", "CustomProperty2") -Sort This example retrieves properties of $myObject and adds custom properties "CustomProperty1" and "CustomProperty2" to the output, sorted alphabetically. #> [CmdletBinding()] param ( [object] $Object, [string[]] $AddProperties, # provides ability to add some custom properties [switch] $Sort ) $Data = @{ } $Properties = New-ArrayList $HighestCount = 0 foreach ($O in $Object) { $ObjectProperties = $O.PSObject.Properties.Name $Count = $ObjectProperties.Count if ($Count -gt $HighestCount) { $Data.HighestCount = $Count $Data.HighestObject = $O $HighestCount = $Count } foreach ($Property in $ObjectProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique } } foreach ($Property in $AddProperties) { Add-ToArrayAdvanced -List $Properties -Element $Property -SkipNull -RequireUnique } $Data.Properties = if ($Sort) { $Properties | Sort-Object } else { $Properties } return $Data } function Get-ObjectTitles { <# .SYNOPSIS Retrieves the titles of properties from an object. .DESCRIPTION This function retrieves the titles of properties from an object and returns them in an ArrayList. .PARAMETER Object Specifies the object from which to retrieve property titles. .EXAMPLE $object = [PSCustomObject]@{ Name = "John Doe" Age = 30 City = "New York" } Get-ObjectTitles -Object $object Description ----------- Retrieves the property titles from the $object and returns them in an ArrayList. #> [CmdletBinding()] param( $Object ) $ArrayList = New-Object System.Collections.ArrayList Write-Verbose "Get-ObjectTitles - ObjectType $($Object.GetType())" foreach ($Title in $Object.PSObject.Properties) { Write-Verbose "Get-ObjectTitles - Value added to array: $($Title.Name)" $ArrayList.Add($Title.Name) | Out-Null } Write-Verbose "Get-ObjectTitles - Array size: $($ArrayList.Count)" return $ArrayList } function Get-ObjectType { <# .SYNOPSIS Retrieves information about the type of the given object. .DESCRIPTION This function retrieves information about the type of the specified object, including its name, base type, and system type. .PARAMETER Object The object for which type information is to be retrieved. .PARAMETER ObjectName The name of the object. Default is 'Random Object Name'. .PARAMETER VerboseOnly Indicates whether to output verbose information only. .EXAMPLE Get-ObjectType -Object $myObject Retrieves type information for the object stored in $myObject. .EXAMPLE Get-ObjectType -Object $myObject -ObjectName "My Custom Object" Retrieves type information for the object stored in $myObject with a custom name. #> [CmdletBinding()] param( [Object] $Object, [string] $ObjectName = 'Random Object Name', [switch] $VerboseOnly ) $ReturnData = [ordered] @{} $ReturnData.ObjectName = $ObjectName if ($null -ne $Object) { try { $TypeInformation = $Object.GetType() $ReturnData.ObjectTypeName = $TypeInformation.Name $ReturnData.ObjectTypeBaseName = $TypeInformation.BaseType $ReturnData.SystemType = $TypeInformation.UnderlyingSystemType } catch { $ReturnData.ObjectTypeName = '' $ReturnData.ObjectTypeBaseName = '' $ReturnData.SystemType = '' } try { $TypeInformationInsider = $Object[0].GetType() $ReturnData.ObjectTypeInsiderName = $TypeInformationInsider.Name $ReturnData.ObjectTypeInsiderBaseName = $TypeInformationInsider.BaseType $ReturnData.SystemTypeInsider = $TypeInformationInsider.UnderlyingSystemType } catch { $ReturnData.ObjectTypeInsiderName = '' $ReturnData.ObjectTypeInsiderBaseName = '' $ReturnData.SystemTypeInsider = '' } } else { $ReturnData.ObjectTypeName = '' $ReturnData.ObjectTypeBaseName = '' $ReturnData.SystemType = '' $ReturnData.ObjectTypeInsiderName = '' $ReturnData.ObjectTypeInsiderBaseName = '' $ReturnData.SystemTypeInsider = '' } Write-Verbose "Get-ObjectType - ObjectTypeName: $($ReturnData.ObjectTypeName)" Write-Verbose "Get-ObjectType - ObjectTypeBaseName: $($ReturnData.ObjectTypeBaseName)" Write-Verbose "Get-ObjectType - SystemType: $($ReturnData.SystemType)" Write-Verbose "Get-ObjectType - ObjectTypeInsiderName: $($ReturnData.ObjectTypeInsiderName)" Write-Verbose "Get-ObjectType - ObjectTypeInsiderBaseName: $($ReturnData.ObjectTypeInsiderBaseName)" Write-Verbose "Get-ObjectType - SystemTypeInsider: $($ReturnData.SystemTypeInsider)" if ($VerboseOnly) { return } else { return Format-TransposeTable -Object $ReturnData } } Function Get-Types { <# .SYNOPSIS Retrieves the enum values of the specified types. .DESCRIPTION This function takes an array of types and returns the enum values of each type. .PARAMETER Types Specifies the types for which enum values need to be retrieved. .EXAMPLE Get-Types -Types [System.DayOfWeek] Retrieves the enum values of the System.DayOfWeek type. .EXAMPLE Get-Types -Types [System.ConsoleColor, System.EnvironmentVariableTarget] Retrieves the enum values of the System.ConsoleColor and System.EnvironmentVariableTarget types. #> [CmdletBinding()] param ( [Object] $Types ) $TypesRequired = foreach ($Type in $Types) { $Type.GetEnumValues() } return $TypesRequired } function Join-Uri { <# .SYNOPSIS Provides ability to join two Url paths together .DESCRIPTION Provides ability to join two Url paths together .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url .EXAMPLE Join-Uri 'https://evotec.xyz/' '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri 'https://evotec.xyz/' 'wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .EXAMPLE Join-Uri -BaseUri 'https://evotec.xyz/test/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-Url')] [cmdletBinding()] param( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory)][uri] $RelativeOrAbsoluteUri ) return ($BaseUri.OriginalString.TrimEnd('/') + "/" + $RelativeOrAbsoluteUri.OriginalString.TrimStart('/')) } function Join-UriQuery { <# .SYNOPSIS Provides ability to join two Url paths together including advanced querying .DESCRIPTION Provides ability to join two Url paths together including advanced querying which is useful for RestAPI/GraphApi calls .PARAMETER BaseUri Primary Url to merge .PARAMETER RelativeOrAbsoluteUri Additional path to merge with primary url (optional) .PARAMETER QueryParameter Parameters and their values in form of hashtable .PARAMETER EscapeUriString If set, will escape the url string .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz/wp-json/wp/v2/posts' -QueryParameter @{ page = 1 per_page = 20 search = 'SearchString' } .EXAMPLE Join-UriQuery -BaseUri 'https://evotec.xyz' -RelativeOrAbsoluteUri '/wp-json/wp/v2/posts' .NOTES General notes #> [alias('Join-UrlQuery')] [CmdletBinding()] param ( [parameter(Mandatory)][uri] $BaseUri, [parameter(Mandatory = $false)][uri] $RelativeOrAbsoluteUri, [Parameter()][System.Collections.IDictionary] $QueryParameter, [alias('EscapeUrlString')][switch] $EscapeUriString ) Begin { Add-Type -AssemblyName System.Web } Process { if ($BaseUri -and $RelativeOrAbsoluteUri) { $Url = Join-Uri -BaseUri $BaseUri -RelativeOrAbsoluteUri $RelativeOrAbsoluteUri } else { $Url = $BaseUri } if ($QueryParameter) { $Collection = [System.Web.HttpUtility]::ParseQueryString([String]::Empty) foreach ($key in $QueryParameter.Keys) { $Collection.Add($key, $QueryParameter.$key) } } $uriRequest = [System.UriBuilder] $Url if ($Collection) { $uriRequest.Query = $Collection.ToString() } if (-not $EscapeUriString) { $uriRequest.Uri.AbsoluteUri } else { [System.Uri]::EscapeUriString($uriRequest.Uri.AbsoluteUri) } } } function Merge-Array { <# .SYNOPSIS Merge-Array allows to merge two or more arrays together. .DESCRIPTION Merge-Array allows to merge two or more arrays together. It copies headers from each Array and merges them together allowing for fulll output. When used with Prescan parameter it actually is able to show headers from all arrays .PARAMETER Array List of Arrays to process .PARAMETER Prescan Scans each element of each array for headers. .EXAMPLE $Array1 = @( [PSCustomObject] @{ Name = 'Company1'; Count = 259 } [PSCustomObject] @{ Name = 'Company2'; Count = 300 } ) $Array2 = @( [PSCustomObject] @{ Name = 'Company1'; Count = 25 } [PSCustomObject] @{ Name = 'Company2'; Count = 100 } ) $Array3 = @( [PSCustomObject] @{ Name1 = 'Company1'; Count3 = 25 } [PSCustomObject] @{ Name1 = 'Company2'; Count3 = 100 } [PSCustomObject] @{ Name2 = 'Company2'; Count3 = 100 } ) $Array1 | Format-Table -AutoSize $Array2 | Format-Table -AutoSize $Array3 | Format-Table -AutoSize Merge-Array -Array $Array1, $Array2, $Array3 | Format-Table -AutoSize Merge-Array -Array $Array1, $Array2, $Array3 -Prescan | Format-Table -AutoSize .NOTES General notes #> param( [Array[]] $Array, [switch] $Prescan ) $PropertyNames = foreach ($A in $Array) { if ($Prescan) { foreach ($O in $A) { $O.PSObject.Properties.Name } } else { $A[0].PSObject.Properties.Name } } $PropertyNames = $PropertyNames | Sort-Object -Unique foreach ($A in $Array) { $A | Select-Object -Property $PropertyNames } } function Merge-Objects { <# .SYNOPSIS Merges two objects into a single object. .DESCRIPTION This function merges two objects into a single object by combining their properties. If there are duplicate properties, the values from Object2 will overwrite the values from Object1. .PARAMETER Object1 The first object to be merged. .PARAMETER Object2 The second object to be merged. .EXAMPLE $Object1 = [pscustomobject] @{ 'Name' = 'John Doe' 'Age' = 30 } $Object2 = [pscustomobject] @{ 'Age' = 31 'City' = 'New York' } Merge-Objects -Object1 $Object1 -Object2 $Object2 Description ----------- Merges $Object1 and $Object2 into a single object. The resulting object will have properties 'Name', 'Age', and 'City' with values 'John Doe', 31, and 'New York' respectively. #> [CmdletBinding()] param ( [Object] $Object1, [Object] $Object2 ) $Object = [ordered] @{} foreach ($Property in $Object1.PSObject.Properties) { $Object.($Property.Name) = $Property.Value } foreach ($Property in $Object2.PSObject.Properties) { $Object.($Property.Name) = $Property.Value } return [pscustomobject] $Object } function New-ArrayList { <# .SYNOPSIS Creates a new ArrayList object. .DESCRIPTION This function creates a new instance of the ArrayList class from the System.Collections namespace. .EXAMPLE $myList = New-ArrayList $myList.Add("Apple") $myList.Add("Banana") $myList.Add("Orange") $myList #> [CmdletBinding()] param() $List = [System.Collections.ArrayList]::new() return , $List } function New-GenericList { <# .SYNOPSIS Creates a new instance of a generic list. .DESCRIPTION This function creates a new instance of a generic list based on the specified type. .PARAMETER Type Specifies the type of objects that the generic list will hold. Defaults to [System.Object]. .EXAMPLE PS C:\> $list = New-GenericList -Type [int] Creates a new generic list that holds integers. .EXAMPLE PS C:\> $list = New-GenericList Creates a new generic list that holds objects. #> [CmdletBinding()] param( [Object] $Type = [System.Object] ) return New-Object "System.Collections.Generic.List[$Type]" } function Remove-DuplicateObjects { <# .SYNOPSIS Removes duplicate objects from a list based on specified properties. .DESCRIPTION This function removes duplicate objects from the input list based on the specified properties. It retains only unique objects in the list. .PARAMETER Object The list of objects to remove duplicates from. .PARAMETER Property The properties to consider when identifying duplicates. .EXAMPLE $Array = @() $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} $Array += [PSCustomObject] @{ 'Name' = 'Test1'; 'Val1' = 'Testor2'; 'Val2' = 'Testor2'} Write-Color 'Before' -Color Red $Array | Format-Table -A Write-Color 'After' -Color Green $Array = $Array | Sort-Object -Unique -Property 'Name', 'Val1','Val2' $Array | Format-Table -AutoSize .NOTES This function removes duplicate objects from a list based on specified properties. #> [CmdletBinding()] param( [System.Collections.IList] $Object, [string[]] $Property ) if ($Object.Count -eq 0) { return $Object } else { return $Object | Sort-Object -Property $Property -Unique } } function Remove-EmptyValue { <# .SYNOPSIS Removes empty values from a hashtable recursively. .DESCRIPTION This function removes empty values from a given hashtable. It can be used to clean up a hashtable by removing keys with null, empty string, empty array, or empty dictionary values. The function supports recursive removal of empty values. .PARAMETER Hashtable The hashtable from which empty values will be removed. .PARAMETER ExcludeParameter An array of keys to exclude from the removal process. .PARAMETER Recursive Indicates whether to recursively remove empty values from nested hashtables. .PARAMETER Rerun Specifies the number of times to rerun the removal process recursively. .PARAMETER DoNotRemoveNull If specified, null values will not be removed. .PARAMETER DoNotRemoveEmpty If specified, empty string values will not be removed. .PARAMETER DoNotRemoveEmptyArray If specified, empty array values will not be removed. .PARAMETER DoNotRemoveEmptyDictionary If specified, empty dictionary values will not be removed. .EXAMPLE $hashtable = @{ 'Key1' = ''; 'Key2' = $null; 'Key3' = @(); 'Key4' = @{} } Remove-EmptyValue -Hashtable $hashtable -Recursive Description ----------- This example removes empty values from the $hashtable recursively. #> [alias('Remove-EmptyValues')] [CmdletBinding()] param( [alias('Splat', 'IDictionary')][Parameter(Mandatory)][System.Collections.IDictionary] $Hashtable, [string[]] $ExcludeParameter, [switch] $Recursive, [int] $Rerun, [switch] $DoNotRemoveNull, [switch] $DoNotRemoveEmpty, [switch] $DoNotRemoveEmptyArray, [switch] $DoNotRemoveEmptyDictionary ) foreach ($Key in [string[]] $Hashtable.Keys) { if ($Key -notin $ExcludeParameter) { if ($Recursive) { if ($Hashtable[$Key] -is [System.Collections.IDictionary]) { if ($Hashtable[$Key].Count -eq 0) { if (-not $DoNotRemoveEmptyDictionary) { $Hashtable.Remove($Key) } } else { Remove-EmptyValue -Hashtable $Hashtable[$Key] -Recursive:$Recursive } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } else { if (-not $DoNotRemoveNull -and $null -eq $Hashtable[$Key]) { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmpty -and $Hashtable[$Key] -is [string] -and $Hashtable[$Key] -eq '') { $Hashtable.Remove($Key) } elseif (-not $DoNotRemoveEmptyArray -and $Hashtable[$Key] -is [System.Collections.IList] -and $Hashtable[$Key].Count -eq 0) { $Hashtable.Remove($Key) } } } } if ($Rerun) { for ($i = 0; $i -lt $Rerun; $i++) { Remove-EmptyValue -Hashtable $Hashtable -Recursive:$Recursive } } } function Remove-FromArray { <# .SYNOPSIS Removes an element from an ArrayList. .DESCRIPTION This function removes a specified element from an ArrayList. It can remove either a specific element or the last element in the list. .PARAMETER List The ArrayList from which the element will be removed. .PARAMETER Element The element to be removed from the ArrayList. .PARAMETER LastElement If this switch is used, the last element in the ArrayList will be removed. .EXAMPLE $myList = New-Object System.Collections.ArrayList $myList.Add("Apple") $myList.Add("Banana") Remove-FromArray -List $myList -Element "Banana" # This will remove the element "Banana" from the ArrayList. .EXAMPLE $myList = New-Object System.Collections.ArrayList $myList.Add("Apple") $myList.Add("Banana") Remove-FromArray -List $myList -LastElement # This will remove the last element in the ArrayList. #> [CmdletBinding()] param( [System.Collections.ArrayList] $List, [Object] $Element, [switch] $LastElement ) if ($LastElement) { $LastID = $List.Count - 1 $List.RemoveAt($LastID) > $null } else { $List.Remove($Element) > $null } } function Remove-ObjectsExistingInTarget { <# .SYNOPSIS Removes objects from the source list that exist in the target list based on specified properties. .DESCRIPTION This function compares two lists of objects and removes objects from the source list that have matching properties in the target list. It returns either the objects that do not exist in the target list or only the objects that exist in the target list based on the specified properties. .PARAMETER ObjectSource The list of objects to compare against the target list. .PARAMETER ObjectTarget The list of objects to compare with. .PARAMETER ComparePropertySource The property in the source objects to compare. .PARAMETER ComparePropertyTarget The property in the target objects to compare. .PARAMETER Reverse Switch to return only the objects that exist in the target list. .EXAMPLE $sourceList = @( [PSCustomObject]@{Id = 1; Name = "A"}, [PSCustomObject]@{Id = 2; Name = "B"}, [PSCustomObject]@{Id = 3; Name = "C"} ) $targetList = @( [PSCustomObject]@{Id = 2; Name = "B"}, [PSCustomObject]@{Id = 3; Name = "C"} ) Remove-ObjectsExistingInTarget -ObjectSource $sourceList -ObjectTarget $targetList -ComparePropertySource "Id" -ComparePropertyTarget "Id" # Output: Id Name # -- ---- # 1 A .EXAMPLE $sourceList = @( [PSCustomObject]@{Id = 1; Name = "A"}, [PSCustomObject]@{Id = 2; Name = "B"}, [PSCustomObject]@{Id = 3; Name = "C"} ) $targetList = @( [PSCustomObject]@{Id = 2; Name = "B"}, [PSCustomObject]@{Id = 3; Name = "C"} ) Remove-ObjectsExistingInTarget -ObjectSource $sourceList -ObjectTarget $targetList -ComparePropertySource "Id" -ComparePropertyTarget "Id" -Reverse # Output: Id Name # -- ---- # 2 B # 3 C #> param( $ObjectSource, $ObjectTarget, [string] $ComparePropertySource, [string] $ComparePropertyTarget, [switch] $Reverse # returns only existing objects ) $ObjectsExistingInTarget = @() $ObjectsNotExistingInTarget = @() foreach ($Object in $ObjectSource) { if ($ObjectTarget.$ComparePropertySource -contains $Object.$ComparePropertyTarget) { $ObjectsExistingInTarget += $Object } else { $ObjectsNotExistingInTarget += $Object } } if ($Reverse) { return $ObjectsExistingInTarget } else { return $ObjectsNotExistingInTarget } } function Remove-WhiteSpace { <# .SYNOPSIS Removes leading, trailing, and extra white spaces from a given text string. .DESCRIPTION The Remove-WhiteSpace function removes any leading, trailing, and extra white spaces from the input text string. It ensures that only single spaces separate words within the text. .PARAMETER Text The input text string from which white spaces are to be removed. .EXAMPLE $MyValue = Remove-WhiteSpace -Text ' My Field ' # $MyValue now contains 'My Field' #> param( [string] $Text ) $Text = $Text -replace '(^\s+|\s+$)', '' -replace '\s+', ' ' return $Text } Function Rename-LatinCharacters { <# .SYNOPSIS Renames a name to a name without special chars. .DESCRIPTION Renames a name to a name without special chars. .PARAMETER String Provide a string to rename .EXAMPLE Rename-LatinCharacters -String 'Przemysław Kłys' .EXAMPLE Rename-LatinCharacters -String 'Przemysław' .NOTES General notes #> [alias('Remove-StringLatinCharacters')] [cmdletBinding()] param( [string] $String ) [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String)) } function Rename-UserValuesFromHash { <# .SYNOPSIS This function renames user values based on a hash table of match data. .DESCRIPTION The Rename-UserValuesFromHash function takes a list of users, a hash table of match data, and an array of field types. It then renames specific values in the user objects based on the match data provided. .PARAMETER Users The list of user objects to be processed. .PARAMETER MatchData A hash table containing the match data used for renaming values. .PARAMETER FieldTypes An array of field types to be considered for renaming. .EXAMPLE $users = @( [PSCustomObject]@{ UserPrincipalName = 'user1@test.com'; License = 'test:license'; ProxyAddress = 'proxy@test.com' } [PSCustomObject]@{ UserPrincipalName = 'user2@test.com'; License = 'test:license'; ProxyAddress = 'proxy@test.com' } ) $matchData = @{ 'test.com' = 'newdomain.com' 'test:' = 'newdomain:' } $fieldTypes = @('UserPrincipalName', 'License') Rename-UserValuesFromHash -Users $users -MatchData $matchData -FieldTypes $fieldTypes #> [CmdletBinding()] param( $Users, $MatchData, $FieldTypes ) Write-Verbose "FieldTypes: $($FieldTypes -join ',')" foreach ($User in $Users) { foreach ($Match in $MatchData.Keys) { $Key = $Match $Value = $MatchData.$Match Write-Verbose "User: $($User.UserPrincipalName) Key: $Key Value: $Value" foreach ($Field in $FieldTypes) { if ($User.$Field) { $User.$Field = $($User.$Field).ToLower().Replace($Key, $Value) } } } } return $Users } function Select-Properties { <# .SYNOPSIS Allows for easy selecting property names from one or multiple objects .DESCRIPTION Allows for easy selecting property names from one or multiple objects. This is especially useful with using AllProperties parameter where we want to make sure to get all properties from all objects. .PARAMETER Objects One or more objects .PARAMETER Property Properties to include .PARAMETER ExcludeProperty Properties to exclude .PARAMETER AllProperties All unique properties from all objects .PARAMETER PropertyNameReplacement Default property name when object has no properties .EXAMPLE $Object1 = [PSCustomobject] @{ Name1 = '1' Name2 = '3' Name3 = '5' } $Object2 = [PSCustomobject] @{ Name4 = '2' Name5 = '6' Name6 = '7' } Select-Properties -Objects $Object1, $Object2 -AllProperties #OR: $Object1, $Object2 | Select-Properties -AllProperties -ExcludeProperty Name6 -Property Name3 .EXAMPLE $Object3 = [Ordered] @{ Name1 = '1' Name2 = '3' Name3 = '5' } $Object4 = [Ordered] @{ Name4 = '2' Name5 = '6' Name6 = '7' } Select-Properties -Objects $Object3, $Object4 -AllProperties $Object3, $Object4 | Select-Properties -AllProperties .NOTES General notes #> [CmdLetBinding()] param( [Array][Parameter(Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] $Objects, [string[]] $Property, [string[]] $ExcludeProperty, [switch] $AllProperties, [string] $PropertyNameReplacement = '*' ) Begin { function Select-Unique { [CmdLetBinding()] param( [System.Collections.IList] $Object ) [Array] $CleanedList = foreach ($O in $Object) { if ($null -ne $O) { $O } } $New = $CleanedList.ToLower() | Select-Object -Unique $Selected = foreach ($_ in $New) { $Index = $Object.ToLower().IndexOf($_) if ($Index -ne -1) { $Object[$Index] } } $Selected } $ObjectsList = [System.Collections.Generic.List[Object]]::new() } Process { foreach ($Object in $Objects) { $ObjectsList.Add($Object) } } End { if ($ObjectsList.Count -eq 0) { Write-Warning 'Select-Properties - Unable to process. Objects count equals 0.' return } if ($ObjectsList[0] -is [System.Collections.IDictionary]) { if ($AllProperties) { [Array] $All = foreach ($_ in $ObjectsList) { $_.Keys } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $ObjectsList[0].Keys } if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_ -and $ExcludeProperty -notcontains $_) { $_ continue } } } elseif ($Property.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($Property -contains $_) { $_ continue } } } elseif ($ExcludeProperty.Count -gt 0) { $FirstObjectProperties = foreach ($_ in $FirstObjectProperties) { if ($ExcludeProperty -notcontains $_) { $_ continue } } } } elseif ($ObjectsList[0].GetType().Name -match 'bool|byte|char|datetime|decimal|double|ExcelHyperLink|float|int|long|sbyte|short|string|timespan|uint|ulong|URI|ushort') { $FirstObjectProperties = $PropertyNameReplacement } else { if ($Property.Count -gt 0 -and $ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty } elseif ($Property.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property $Property } elseif ($ExcludeProperty.Count -gt 0) { $ObjectsList = $ObjectsList | Select-Object -Property '*' -ExcludeProperty $ExcludeProperty } if ($AllProperties) { [Array] $All = foreach ($_ in $ObjectsList) { $ListProperties = $_.PSObject.Properties.Name if ($null -ne $ListProperties) { $ListProperties } } $FirstObjectProperties = Select-Unique -Object $All } else { $FirstObjectProperties = $ObjectsList[0].PSObject.Properties.Name } } $FirstObjectProperties } } function Split-Array { <# .SYNOPSIS Split an array into multiple arrays of a specified size or by a specified number of elements .DESCRIPTION Split an array into multiple arrays of a specified size or by a specified number of elements .PARAMETER Objects Lists of objects you would like to split into multiple arrays based on their size or number of parts to split into. .PARAMETER Parts Parameter description .PARAMETER Size Parameter description .EXAMPLE This splits array into multiple arrays of 3 Example below wil return 1,2,3 + 4,5,6 + 7,8,9 Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Parts 3 .EXAMPLE This splits array into 3 parts regardless of amount of elements Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Size 3 .NOTES #> [CmdletBinding()] param( [alias('InArray', 'List')][Array] $Objects, [int]$Parts, [int]$Size ) if ($Objects.Count -eq 1) { return $Objects } if ($Parts) { $PartSize = [Math]::Ceiling($inArray.count / $Parts) } if ($Size) { $PartSize = $Size $Parts = [Math]::Ceiling($Objects.count / $Size) } $outArray = [System.Collections.Generic.List[Object]]::new() for ($i = 1; $i -le $Parts; $i++) { $start = (($i - 1) * $PartSize) $end = (($i) * $PartSize) - 1 if ($end -ge $Objects.count) { $end = $Objects.count - 1 } $outArray.Add(@($Objects[$start..$end])) } , $outArray } function Test-IsDistinguishedName { <# .SYNOPSIS Determines whether a given string is a valid Distinguished Name (DN) format. .DESCRIPTION This function checks if the provided string matches the format of a Distinguished Name (DN) in Active Directory. It validates the structure of a DN which typically consists of Common Name (CN), Organizational Unit (OU), and Domain Component (DC) components. .PARAMETER Identity Specifies the string to be tested as a Distinguished Name (DN). .EXAMPLE Test-IsDistinguishedName -Identity "CN=John Doe,OU=Users,DC=example,DC=com" This example checks if the given string is a valid Distinguished Name format. .NOTES Original source: https://github.com/PsCustomObject/IT-ToolBox/blob/master/Public/Test-IsValidDn.ps1 #> [alias('Test-IsDN')] [cmdletBinding()] param ( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [Alias('DN', 'DistinguishedName')][string] $Identity ) Process { [regex]$distinguishedNameRegex = '^(?:(?<cn>CN=(?<name>(?:[^,]|\,)*)),)?(?:(?<path>(?:(?:CN|OU)=(?:[^,]|\,)+,?)+),)?(?<domain>(?:DC=(?:[^,]|\,)+,?)+)$' $Identity -match $distinguishedNameRegex } } function Find-MyProgramData { <# .SYNOPSIS Finds specific data within a given array of strings. .DESCRIPTION This function searches for a specific text within an array of strings and returns the second element of the string that matches the search criteria. .PARAMETER Data The array of strings to search through. .PARAMETER FindText The text to search for within the array of strings. .EXAMPLE Find-MyProgramData -Data @("Program A 123", "Program B 456", "Program C 789") -FindText "B" This example will return "456" as it finds the string containing "B" and returns the second element of that string. #> [CmdletBinding()] param ( $Data, $FindText ) foreach ($Sub in $Data) { if ($Sub -like $FindText) { $Split = $Sub.Split(' ') return $Split[1] } } return '' } function Initialize-ModulePortable { <# .SYNOPSIS Initializes a portable module by downloading or importing it along with its required modules. .DESCRIPTION This function initializes a portable module by either downloading it from the PowerShell Gallery or importing it from a specified path. It also recursively loads any required modules for the primary module. .PARAMETER Name Specifies the name of the module to initialize. .PARAMETER Path Specifies the path where the module will be downloaded or imported. Defaults to the current script root. .PARAMETER Download Switch to indicate whether to download the module from the PowerShell Gallery. .PARAMETER Import Switch to indicate whether to import the module from the specified path. .EXAMPLE Initialize-ModulePortable -Name "MyModule" -Download Downloads the module named "MyModule" from the PowerShell Gallery. .EXAMPLE Initialize-ModulePortable -Name "MyModule" -Path "C:\Modules" -Import Imports the module named "MyModule" from the specified path "C:\Modules". #> [CmdletBinding()] param( [alias('ModuleName')][string] $Name, [string] $Path = $PSScriptRoot, [switch] $Download, [switch] $Import ) function Get-RequiredModule { param( [string] $Path, [string] $Name ) $PrimaryModule = Get-ChildItem -LiteralPath "$Path\$Name" -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1 if ($PrimaryModule) { $Module = Get-Module -ListAvailable $PrimaryModule.FullName -ErrorAction SilentlyContinue -Verbose:$false if ($Module) { [Array] $RequiredModules = $Module.RequiredModules.Name if ($null -ne $RequiredModules) { $null } $RequiredModules foreach ($_ in $RequiredModules) { Get-RequiredModule -Path $Path -Name $_ } } } else { Write-Warning "Initialize-ModulePortable - Modules to load not found in $Path" } } if (-not $Name) { Write-Warning "Initialize-ModulePortable - Module name not given. Terminating." return } if (-not $Download -and -not $Import) { Write-Warning "Initialize-ModulePortable - Please choose Download/Import switch. Terminating." return } if ($Download) { try { if (-not $Path -or -not (Test-Path -LiteralPath $Path)) { $null = New-Item -ItemType Directory -Path $Path -Force } Save-Module -Name $Name -LiteralPath $Path -WarningVariable WarningData -WarningAction SilentlyContinue -ErrorAction Stop } catch { $ErrorMessage = $_.Exception.Message if ($WarningData) { Write-Warning "Initialize-ModulePortable - $WarningData" } Write-Warning "Initialize-ModulePortable - Error $ErrorMessage" return } } if ($Download -or $Import) { [Array] $Modules = Get-RequiredModule -Path $Path -Name $Name | Where-Object { $null -ne $_ } if ($null -ne $Modules) { [array]::Reverse($Modules) } $CleanedModules = [System.Collections.Generic.List[string]]::new() foreach ($_ in $Modules) { if ($CleanedModules -notcontains $_) { $CleanedModules.Add($_) } } $CleanedModules.Add($Name) $Items = foreach ($_ in $CleanedModules) { Get-ChildItem -LiteralPath "$Path\$_" -Filter '*.psd1' -Recurse -ErrorAction SilentlyContinue -Depth 1 } [Array] $PSD1Files = $Items.FullName } if ($Download) { $ListFiles = foreach ($PSD1 in $PSD1Files) { $PSD1.Replace("$Path", '$PSScriptRoot') } $Content = @( '$Modules = @(' foreach ($_ in $ListFiles) { " `"$_`"" } ')' "foreach (`$_ in `$Modules) {" " Import-Module `$_ -Verbose:`$false -Force" "}" ) $Content | Set-Content -Path $Path\$Name.ps1 -Force } if ($Import) { $ListFiles = foreach ($PSD1 in $PSD1Files) { $PSD1 } foreach ($_ in $ListFiles) { Import-Module $_ -Verbose:$false -Force } } } function Invoke-CommandCustom { <# .SYNOPSIS Invokes a script block with optional parameters and arguments. .DESCRIPTION The Invoke-CommandCustom function executes a script block with the ability to pass parameters and arguments. It provides options to return verbose output, errors, and warnings. .PARAMETER ScriptBlock Specifies the script block to execute. .PARAMETER Parameter Specifies a dictionary of parameters to pass to the script block. .PARAMETER Argument Specifies an array of arguments to pass to the script block. .PARAMETER ReturnVerbose Indicates whether to return verbose output. .PARAMETER ReturnError Indicates whether to return errors. .PARAMETER ReturnWarning Indicates whether to return warnings. .PARAMETER AddParameter Indicates whether to add parameters to the script block. .EXAMPLE Invoke-CommandCustom -ScriptBlock { Get-Process } -ReturnVerbose Description: Invokes the Get-Process cmdlet and returns verbose output. .EXAMPLE Invoke-CommandCustom -ScriptBlock { Get-Service } -Parameter @{Name="Spooler"} -ReturnError Description: Invokes the Get-Service cmdlet with the "Spooler" parameter and returns any errors encountered. #> [cmdletBinding()] param( [scriptblock] $ScriptBlock, [System.Collections.IDictionary] $Parameter, [array] $Argument, [switch] $ReturnVerbose, [switch] $ReturnError, [switch] $ReturnWarning, [switch] $AddParameter ) $Output = [ordered]@{} $ps = [PowerShell]::Create() if ($ReturnVerbose) { $null = $ps.AddScript('$VerbosePreference = "Continue"').AddStatement() } if ($ScriptBlock) { if ($Parameter -and $AddParameter) { $Count = 0 [string] $ScriptBlockParams = @( "param(" foreach ($Key in $Parameter.Keys) { $Count++ if ($Count -eq $Parameter.Keys.Count) { "`$$($Key)" } else { "`$$($Key)," } } ")" $ScriptBlock.ToString() ) $ScriptBlockScript = [scriptblock]::Create($ScriptBlockParams) $null = $ps.AddScript($ScriptBlockScript) } else { $null = $ps.AddScript($ScriptBlock) } } if ($Parameter) { foreach ($Key in $Parameter.Keys) { $null = $ps.AddParameter($Key, $Parameter[$Key]) } } if ($Argument) { foreach ($Arg in $Argument) { $null = $ps.AddArgument($Arg) } } $ErrorCaught = $null try { $InvokedCommand = $ps.Invoke() } catch { $ErrorCaught = $_ } if ($InvokedCommand) { $Output['Output'] = $InvokedCommand } if ($ReturnVerbose) { if ($Ps.Streams.Verbose) { $Output['Verbose'] = $ps.Streams.Verbose } } if ($ReturnWarning) { if ($ps.Streams.Warning) { $Output['Warning'] = $ps.Streams.Warning } } if ($ReturnError) { if ($ErrorCaught) { $Output['Error'] = $ErrorCaught } else { if ($Ps.Streams.Error) { $Output['Error'] = $ps.Streams.Error } } } if ($ReturnError -or $ReturnVerbose -or $ReturnWarning) { $Output } else { $Output.Output } } function Start-InternalFunction { <# .SYNOPSIS Starts an internal function within a specified module. .DESCRIPTION This function starts an internal function within a specified module by importing the module and executing the provided script block. .PARAMETER ScriptBlock Specifies the script block to be executed as the internal function. .PARAMETER Module Specifies the name of the module containing the internal function. .EXAMPLE Start-InternalFunction -ScriptBlock { Get-ChildItem } -Module "ExampleModule" This example starts the internal function 'Get-ChildItem' within the 'ExampleModule' module. #> [CmdletBinding()] param( [ScriptBlock] $ScriptBlock, [string] $Module ) $InternalModule = Import-Module -Name $Module -PassThru & $InternalModule $ScriptBlock } function Start-MyProgram { <# .SYNOPSIS Starts a program with specified arguments and logs the output. .DESCRIPTION This function starts a program with the provided arguments and logs the output using a specified logger. If no logger is provided, it returns the output as a string. .PARAMETER Program The path to the program to be executed. .PARAMETER CmdArgList An array of arguments to be passed to the program. .PARAMETER LoggerParameters A dictionary containing parameters for the logger. .EXAMPLE Start-MyProgram -Program "C:\Program.exe" -CmdArgList @("-arg1", "-arg2") -LoggerParameters @{"LogPath"="C:\Logs"; "LogLevel"="Info"} Starts the program "C:\Program.exe" with arguments "-arg1" and "-arg2" and logs the output using a logger with log path "C:\Logs" and log level "Info". .EXAMPLE Start-MyProgram -Program "C:\AnotherProgram.exe" -CmdArgList @("-input", "file.txt") Starts the program "C:\AnotherProgram.exe" with argument "-input file.txt" and returns the output as a string. #> [CmdletBinding()] param ( [string] $Program, [string[]] $CmdArgList, [System.Collections.IDictionary] $LoggerParameters ) $Output = (cmd /c $Program $CmdArgList '2>&1') if (-not $LoggerParameters) { if ($Output) { return $Output } } else { $Logger = Get-Logger @LoggerParameters if ($null -ne $Output) { $Logger.AddInfoRecord("Running program $Program with output: $Output") } else { $Logger.AddInfoRecord("Running program $Program $CmdArgList") } } } function Get-RandomCharacters { <# .SYNOPSIS Generates a random string of characters from a specified character set. .DESCRIPTION This function generates a random string of characters from a specified character set with the given length. .PARAMETER length The length of the random string to generate. .PARAMETER characters The set of characters from which to generate the random string. .EXAMPLE Get-RandomCharacters -length 8 -characters 'abcdef123' Generates a random string of 8 characters from the character set 'abcdef123'. .EXAMPLE Get-RandomCharacters -length 12 -characters 'ABC123!@#' Generates a random string of 12 characters from the character set 'ABC123!@#'. #> [cmdletbinding()] param( [int] $length, [string] $characters ) if ($length -ne 0 -and $characters -ne '') { $random = 1..$length | ForEach-Object { Get-Random -Maximum $characters.length } $private:ofs = "" return [String]$characters[$random] } else { return } } function Get-RandomFileName { <# .SYNOPSIS Generates a random file name with a specified length and extension. .DESCRIPTION This function generates a random file name with a specified length and extension. The file name is created using random letters only. .PARAMETER Length The length of the random file name to generate. Default is 16. .PARAMETER Extension The extension to append to the random file name. .EXAMPLE Get-RandomFileName -Length 8 -Extension "txt" Generates a random file name with a length of 8 characters and appends the extension ".txt". .EXAMPLE Get-RandomFileName -Extension "docx" Generates a random file name with a default length of 16 characters and appends the extension ".docx". #> [cmdletbinding()] param( $Length = 16, $Extension ) $File = Get-RandomStringName -Size $Length -LettersOnly -ToLower return "$File.$Extension" } function Get-RandomPassword { <# .SYNOPSIS Generates a random password with a specified number of lowercase letters, uppercase letters, numbers, and special characters. .DESCRIPTION This function generates a random password with a customizable combination of lowercase letters, uppercase letters, numbers, and special characters. .PARAMETER LettersLowerCase Specifies the number of lowercase letters to include in the password. .PARAMETER LettersHigherCase Specifies the number of uppercase letters to include in the password. .PARAMETER Numbers Specifies the number of numbers to include in the password. .PARAMETER SpecialChars Specifies the number of special characters to include in the password. .PARAMETER SpecialCharsLimited Specifies the number of limited special characters to include in the password. .EXAMPLE Get-RandomPassword -LettersLowerCase 4 -LettersHigherCase 2 -Numbers 1 -SpecialChars 0 -SpecialCharsLimited 1 Generates a random password with 4 lowercase letters, 2 uppercase letters, 1 number, and 1 limited special character. .EXAMPLE Get-RandomPassword -LettersLowerCase 3 -LettersHigherCase 3 -Numbers 2 -SpecialChars 2 -SpecialCharsLimited 1 Generates a random password with 3 lowercase letters, 3 uppercase letters, 2 numbers, 2 special characters, and 1 limited special character. #> [cmdletbinding()] param( [int] $LettersLowerCase = 4, [int] $LettersHigherCase = 2, [int] $Numbers = 1, [int] $SpecialChars = 0, [int] $SpecialCharsLimited = 1 ) $Password = @( Get-RandomCharacters -length $LettersLowerCase -characters 'abcdefghiklmnoprstuvwxyz' Get-RandomCharacters -length $LettersHigherCase -characters 'ABCDEFGHKLMNOPRSTUVWXYZ' Get-RandomCharacters -length $Numbers -characters '1234567890' Get-RandomCharacters -length $SpecialChars -characters '!$%()=?{@#' Get-RandomCharacters -length $SpecialCharsLimited -characters '!$#' ) $StringPassword = $Password -join '' $StringPassword = ($StringPassword.ToCharArray() | Get-Random -Count $StringPassword.Length) -join '' return $StringPassword } function Get-RandomStringName { <# .SYNOPSIS Generates a random string of specified length with various options. .DESCRIPTION This function generates a random string of specified length with options to convert the case and include only letters. .PARAMETER Size The length of the random string to generate. Default is 31. .PARAMETER ToLower Convert the generated string to lowercase. .PARAMETER ToUpper Convert the generated string to uppercase. .PARAMETER LettersOnly Generate a random string with only letters. .EXAMPLE Get-RandomStringName -Size 10 Generates a random string of length 10. .EXAMPLE Get-RandomStringName -Size 8 -ToLower Generates a random string of length 8 and converts it to lowercase. .EXAMPLE Get-RandomStringName -Size 12 -ToUpper Generates a random string of length 12 and converts it to uppercase. .EXAMPLE Get-RandomStringName -Size 15 -LettersOnly Generates a random string of length 15 with only letters. #> [cmdletbinding()] param( [int] $Size = 31, [switch] $ToLower, [switch] $ToUpper, [switch] $LettersOnly ) [string] $MyValue = @( if ($LettersOnly) { ( -join ((1..$Size) | ForEach-Object { (65..90) + (97..122) | Get-Random } | ForEach-Object { [char]$_ })) } else { ( -join ((48..57) + (97..122) | Get-Random -Count $Size | ForEach-Object { [char]$_ })) } ) if ($ToLower) { return $MyValue.ToLower() } if ($ToUpper) { return $MyValue.ToUpper() } return $MyValue } function Dismount-PSRegistryPath { <# .SYNOPSIS Dismounts a registry path. .DESCRIPTION This function dismounts a registry path specified by the MountPoint parameter. It unloads the registry path using reg.exe command. .PARAMETER MountPoint Specifies the registry path to be dismounted. .PARAMETER Suppress Suppresses the output if set to $true. .EXAMPLE Dismount-PSRegistryPath -MountPoint "HKLM:\Software\MyApp" -Suppress Dismounts the registry path "HKLM:\Software\MyApp" without displaying any output. .EXAMPLE Dismount-PSRegistryPath -MountPoint "HKCU:\Software\Settings" Dismounts the registry path "HKCU:\Software\Settings" and displays output if successful. #> [alias('Dismount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [switch] $Suppress ) [gc]::Collect() $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " unload $MountPoint" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Dismount-PSRegistryPath - Couldn't unmount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function Get-PSRegistry { <# .SYNOPSIS Get registry key values. .DESCRIPTION Get registry key values. .PARAMETER RegistryPath The registry path to get the values from. .PARAMETER ComputerName The computer to get the values from. If not specified, the local computer is used. .PARAMETER ExpandEnvironmentNames Expand environment names in the registry value. By default it doesn't do that. If you want to expand environment names, use this parameter. .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' -ComputerName AD1 .EXAMPLE Get-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters' .EXAMPLE Get-PSRegistry -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\DFSR\Parameters" -ComputerName AD1,AD2,AD3 | ft -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Windows PowerShell' | Format-Table -AutoSize .EXAMPLE Get-PSRegistry -RegistryPath 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EventLog\Directory Service' -ComputerName AD1 -Advanced .EXAMPLE Get-PSRegistry -RegistryPath "HKLM:\Software\Microsoft\Powershell\1\Shellids\Microsoft.Powershell\" .EXAMPLE # Get default key and it's value Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key "" .EXAMPLE # Get default key and it's value (alternative) Get-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -DefaultKey .NOTES General notes #> [cmdletbinding()] param( [alias('Path')][string[]] $RegistryPath, [string[]] $ComputerName = $Env:COMPUTERNAME, [string] $Key, [switch] $Advanced, [switch] $DefaultKey, [switch] $ExpandEnvironmentNames, [Parameter(DontShow)][switch] $DoNotUnmount ) $Script:CurrentGetCount++ Get-PSRegistryDictionaries $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $Computers = Get-ComputerSplit -ComputerName $ComputerName [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent if ($PSBoundParameters.ContainsKey("Key") -or $DefaultKey) { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary -Key $Key foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistry -Registry $R -ComputerName $Computer -Remote -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } else { [Array] $RegistryValues = Get-PSSubRegistryTranslated -RegistryPath $RegistryTranslated -HiveDictionary $Script:HiveDictionary foreach ($Computer in $Computers[0]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } foreach ($Computer in $Computers[1]) { foreach ($R in $RegistryValues) { Get-PSSubRegistryComplete -Registry $R -ComputerName $Computer -Remote -Advanced:$Advanced -ExpandEnvironmentNames:$ExpandEnvironmentNames.IsPresent } } } $Script:CurrentGetCount-- if ($Script:CurrentGetCount -eq 0) { if (-not $DoNotUnmount) { Unregister-MountedRegistry } } } function Mount-PSRegistryPath { <# .SYNOPSIS Mounts a registry path to a specified location. .DESCRIPTION This function mounts a registry path to a specified location using the reg.exe utility. .PARAMETER MountPoint Specifies the registry mount point where the registry path will be mounted. .PARAMETER FilePath Specifies the file path of the registry hive to be mounted. .EXAMPLE Mount-PSRegistryPath -MountPoint 'HKEY_USERS\.DEFAULT_USER111' -FilePath 'C:\Users\Default\NTUSER.DAT' Mounts the registry hive located at 'C:\Users\Default\NTUSER.DAT' to the registry key 'HKEY_USERS\.DEFAULT_USER111'. .NOTES This function requires administrative privileges to mount registry paths. #> [alias('Mount-RegistryPath')] [cmdletbinding()] param( [Parameter(Mandatory)][string] $MountPoint, [Parameter(Mandatory)][string] $FilePath ) $pinfo = [System.Diagnostics.ProcessStartInfo]::new() $pinfo.FileName = "reg.exe" $pinfo.RedirectStandardError = $true $pinfo.RedirectStandardOutput = $true $pinfo.UseShellExecute = $false $pinfo.Arguments = " load $MountPoint $FilePath" $pinfo.CreateNoWindow = $true $pinfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden $p = [System.Diagnostics.Process]::new() $p.StartInfo = $pinfo $p.Start() | Out-Null $p.WaitForExit() $Output = $p.StandardOutput.ReadToEnd() $Errors = $p.StandardError.ReadToEnd() if ($Errors) { if ($PSBoundParameters.ErrorAction -eq 'Stop') { throw $Errors } else { Write-Warning -Message "Mount-PSRegistryPath - Couldn't mount $MountPoint. $Errors" } } else { if ($Output -like "*operation completed*") { if (-not $Suppress) { return $true } } } if (-not $Suppress) { return $false } } function New-PSRegistry { <# .SYNOPSIS Provides a way to create new registry paths .DESCRIPTION Provides a way to create new registry paths .PARAMETER ComputerName The computer to run the command on. Defaults to local computer. .PARAMETER RegistryPath Registry Path to Create .PARAMETER Suppress Suppresses the output of the command. By default the command outputs PSObject with the results of the operation. .EXAMPLE New-PSRegistry -RegistryPath "HKCU:\\Tests1\CurrentControlSet\Control\Lsa" -Verbose -WhatIf .EXAMPLE New-PSRegistry -RegistryPath "HKCU:\\Tests1\CurrentControlSet\Control\Lsa" .NOTES General notes #> [cmdletbinding(SupportsShouldProcess)] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [Parameter(Mandatory)][string] $RegistryPath, [switch] $Suppress ) Get-PSRegistryDictionaries [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary foreach ($Registry in $RegistryTranslated) { $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -ReverseTypesDictionary $Script:ReverseTypesDictionary if ($RegistryValue.HiveKey) { foreach ($Computer in $ComputersSplit[0]) { New-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } foreach ($Computer in $ComputersSplit[1]) { New-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Remote -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Unregister-MountedRegistry throw } else { Write-Warning "New-PSRegistry - Setting registry to $RegistryPath have failed. Couldn't translate HIVE." } } } Unregister-MountedRegistry } function Remove-PSRegistry { <# .SYNOPSIS Remove registry keys and folders .DESCRIPTION Remove registry keys and folders using .NET methods .PARAMETER ComputerName The computer to run the command on. Defaults to local computer. .PARAMETER RegistryPath The registry path to remove. .PARAMETER Key The registry key to remove. .PARAMETER Recursive Forces deletion of registry folder and all keys, including nested folders .PARAMETER Suppress Suppresses the output of the command. By default the command outputs PSObject with the results of the operation. .EXAMPLE Remove-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests\Ok\MaybeNot" -Recursive .EXAMPLE Remove-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests\Ok\MaybeNot" -Key "LimitBlankPass1wordUse" .EXAMPLE Remove-PSRegistry -RegistryPath "HKCU:\Tests\Ok" .NOTES General notes #> [cmdletBinding(SupportsShouldProcess)] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [Parameter(Mandatory)][string] $RegistryPath, [Parameter()][string] $Key, [switch] $Recursive, [switch] $Suppress ) Get-PSRegistryDictionaries [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary foreach ($Registry in $RegistryTranslated) { $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -ReverseTypesDictionary $Script:ReverseTypesDictionary if ($RegistryValue.HiveKey) { foreach ($Computer in $ComputersSplit[0]) { Remove-PrivateRegistry -Key $Key -RegistryValue $RegistryValue -Computer $Computer -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } foreach ($Computer in $ComputersSplit[1]) { Remove-PrivateRegistry -Key $Key -RegistryValue $RegistryValue -Computer $Computer -Remote -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Unregister-MountedRegistry throw } else { Write-Warning "Remove-PSRegistry - Removing registry $RegistryPath have failed (recursive: $($Recursive.IsPresent)). Couldn't translate HIVE." } } } Unregister-MountedRegistry } function Set-PSRegistry { <# .SYNOPSIS Sets/Updates registry entries locally and remotely using .NET methods. .DESCRIPTION Sets/Updates registry entries locally and remotely using .NET methods. If the registry path to key doesn't exists it will be created. .PARAMETER ComputerName The computer to run the command on. Defaults to local computer. .PARAMETER RegistryPath Registry Path to Update .PARAMETER Type Registry type to use. Options are: REG_SZ, REG_EXPAND_SZ, REG_BINARY, REG_DWORD, REG_MULTI_SZ, REG_QWORD, string, expandstring, binary, dword, multistring, qword .PARAMETER Key Registry key to set. If the path to registry key doesn't exists it will be created. .PARAMETER Value Registry value to set. .PARAMETER Suppress Suppresses the output of the command. By default the command outputs PSObject with the results of the operation. .EXAMPLE Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_DWORD -Key "16 LDAP Interface Events" -Value 2 -ComputerName AD1 .EXAMPLE Set-PSRegistry -RegistryPath 'HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics' -Type REG_SZ -Key "LDAP Interface Events" -Value 'test' -ComputerName AD1 .EXAMPLE Set-PSRegistry -RegistryPath "HKCU:\\Tests" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD .EXAMPLE Set-PSRegistry -RegistryPath "HKCU:\\Tests\MoreTests\Tests1" -Key "LimitBlankPass1wordUse" -Value "0" -Type REG_DWORD .EXAMPLE # Setting default value $ValueData = [byte[]] @( 0, 1, 0, 0, 9, 0, 0, 0, 128, 0, 0, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 0, 0, 5, 0, 10, 0, 14, 0, 3, 0, 5, 0, 6, 0, 6, 0, 4, 0, 4, 0 ) Set-PSRegistry -RegistryPath "HKEY_CURRENT_USER\Tests" -Key '' -Value $ValueData -Type 'NONE' .NOTES General notes #> [cmdletbinding(SupportsShouldProcess)] param( [string[]] $ComputerName = $Env:COMPUTERNAME, [Parameter(Mandatory)][string] $RegistryPath, [Parameter(Mandatory)][ValidateSet('REG_SZ', 'REG_NONE', 'None', 'REG_EXPAND_SZ', 'REG_BINARY', 'REG_DWORD', 'REG_MULTI_SZ', 'REG_QWORD', 'string', 'binary', 'dword', 'qword', 'multistring', 'expandstring')][string] $Type, [Parameter()][string] $Key, [Parameter(Mandatory)][object] $Value, [switch] $Suppress ) Unregister-MountedRegistry Get-PSRegistryDictionaries [Array] $ComputersSplit = Get-ComputerSplit -ComputerName $ComputerName $RegistryPath = Resolve-PrivateRegistry -RegistryPath $RegistryPath [Array] $RegistryTranslated = Get-PSConvertSpecialRegistry -RegistryPath $RegistryPath -Computers $ComputerName -HiveDictionary $Script:HiveDictionary foreach ($Registry in $RegistryTranslated) { $RegistryValue = Get-PrivateRegistryTranslated -RegistryPath $Registry -HiveDictionary $Script:HiveDictionary -Key $Key -Value $Value -Type $Type -ReverseTypesDictionary $Script:ReverseTypesDictionary if ($RegistryValue.HiveKey) { foreach ($Computer in $ComputersSplit[0]) { Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } foreach ($Computer in $ComputersSplit[1]) { Set-PrivateRegistry -RegistryValue $RegistryValue -Computer $Computer -Remote -Suppress:$Suppress.IsPresent -ErrorAction $ErrorActionPreference -WhatIf:$WhatIfPreference } } else { if ($PSBoundParameters.ErrorAction -eq 'Stop') { Unregister-MountedRegistry throw } else { Write-Warning "Set-PSRegistry - Setting registry to $Registry have failed. Couldn't translate HIVE." } } } Unregister-MountedRegistry } function Test-PSRegistry { <# .SYNOPSIS Tests the existence of a specified registry key on a remote or local computer. .DESCRIPTION This function checks if a specified registry key exists on a remote or local computer. .PARAMETER RegistryPath Specifies the path to the registry key(s) to be checked. .PARAMETER ComputerName Specifies the name of the remote computer to check. Defaults to the local computer. .PARAMETER Key Specifies the specific registry key to check for existence. .EXAMPLE Test-PSRegistry -RegistryPath 'HKLM:\Software\Microsoft' -Key 'Windows' Description ----------- Checks if the 'Windows' key exists under 'HKLM:\Software\Microsoft' on the local computer. .EXAMPLE Test-PSRegistry -RegistryPath 'HKLM:\Software\Microsoft' -ComputerName 'RemoteComputer' -Key 'Windows' Description ----------- Checks if the 'Windows' key exists under 'HKLM:\Software\Microsoft' on the 'RemoteComputer'. #> [cmdletbinding()] param( [alias('Path')][string[]] $RegistryPath, [string] $ComputerName = $Env:COMPUTERNAME, [string] $Key ) $Output = Get-PSRegistry -RegistryPath $RegistryPath -ComputerName $ComputerName if ($Output.PSConnection -eq $true -and $Output.PSError -eq $false) { if ($Key) { if ($null -ne $Output.$Key) { return $true } else { return $false } } else { return $true } } else { return $false } } function New-Runspace { <# .SYNOPSIS Creates a new runspace pool with the specified minimum and maximum runspaces. .DESCRIPTION This function creates a new runspace pool with the specified minimum and maximum runspaces. It allows for concurrent execution of PowerShell scripts. .PARAMETER minRunspaces The minimum number of runspaces to be created in the runspace pool. Default is 1. .PARAMETER maxRunspaces The maximum number of runspaces to be created in the runspace pool. Default is the number of processors plus 1. .EXAMPLE $pool = New-Runspace -minRunspaces 2 -maxRunspaces 5 Creates a runspace pool with a minimum of 2 and a maximum of 5 runspaces. .EXAMPLE $pool = New-Runspace Creates a runspace pool with default minimum and maximum runspaces. #> [cmdletbinding()] param ( [int] $minRunspaces = 1, [int] $maxRunspaces = [int]$env:NUMBER_OF_PROCESSORS + 1 ) $RunspacePool = [RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces) $RunspacePool.Open() return $RunspacePool } function Start-Runspace { <# .SYNOPSIS Starts a new runspace with the provided script block, parameters, and runspace pool. .DESCRIPTION This function creates a new runspace using the specified script block, parameters, and runspace pool. It then starts the runspace and returns an object containing the runspace and its status. .PARAMETER ScriptBlock The script block to be executed in the new runspace. .PARAMETER Parameters The parameters to be passed to the script block. .PARAMETER RunspacePool The runspace pool in which the new runspace will be created. .EXAMPLE $scriptBlock = { Get-Process } $parameters = @{ Name = 'explorer.exe' } $runspacePool = [RunspaceFactory]::CreateRunspacePool(1, 5) $runspacePool.Open() $result = Start-Runspace -ScriptBlock $scriptBlock -Parameters $parameters -RunspacePool $runspacePool $result.Pipe | Receive-Job -Wait This example starts a new runspace that retrieves information about the 'explorer.exe' process. #> [cmdletbinding()] param ( [ScriptBlock] $ScriptBlock, [System.Collections.IDictionary] $Parameters, [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool ) if ($ScriptBlock -ne '') { $runspace = [PowerShell]::Create() $null = $runspace.AddScript($ScriptBlock) if ($null -ne $Parameters) { $null = $runspace.AddParameters($Parameters) } $runspace.RunspacePool = $RunspacePool [PSCustomObject]@{ Pipe = $runspace Status = $runspace.BeginInvoke() } } } function Stop-Runspace { <# .SYNOPSIS Stops and cleans up the specified runspaces. .DESCRIPTION This function stops and cleans up the specified runspaces by checking their status and handling any errors, warnings, and verbose messages. It also provides an option for extended output. .PARAMETER Runspaces Specifies the array of runspaces to stop. .PARAMETER FunctionName Specifies the name of the function associated with the runspaces. .PARAMETER RunspacePool Specifies the runspace pool to close and dispose of. .PARAMETER ExtendedOutput Indicates whether to include extended output in the result. .EXAMPLE Stop-Runspace -Runspaces $runspaceArray -FunctionName "MyFunction" -RunspacePool $pool -ExtendedOutput Stops the specified runspaces in the $runspaceArray associated with the function "MyFunction" using the runspace pool $pool and includes extended output. #> [cmdletbinding()] param( [Array] $Runspaces, [string] $FunctionName, [System.Management.Automation.Runspaces.RunspacePool] $RunspacePool, [switch] $ExtendedOutput ) [Array] $List = While (@($Runspaces | Where-Object -FilterScript { $null -ne $_.Status }).count -gt 0) { foreach ($Runspace in $Runspaces | Where-Object { $_.Status.IsCompleted -eq $true }) { $Errors = foreach ($e in $($Runspace.Pipe.Streams.Error)) { Write-Error -ErrorRecord $e $e } foreach ($w in $($Runspace.Pipe.Streams.Warning)) { Write-Warning -Message $w } foreach ($v in $($Runspace.Pipe.Streams.Verbose)) { Write-Verbose -Message $v } if ($ExtendedOutput) { @{ Output = $Runspace.Pipe.EndInvoke($Runspace.Status) Errors = $Errors } } else { $Runspace.Pipe.EndInvoke($Runspace.Status) } $Runspace.Status = $null } } $RunspacePool.Close() $RunspacePool.Dispose() if ($List.Count -eq 1) { return , $List } else { return $List } } function Get-PSService { <# .SYNOPSIS Alternative way to Get-Service .DESCRIPTION Alternative way to Get-Service which works using CIM queries .PARAMETER ComputerName ComputerName(s) to query for services .PARAMETER Protocol Protocol to use to gather services .PARAMETER Service Limit output to just few services .PARAMETER All Return all data without filtering .PARAMETER Extended Return more data .EXAMPLE Get-PSService -ComputerName AD1, AD2, AD3, AD4 -Service 'Dnscache', 'DNS', 'PeerDistSvc', 'WebClient', 'Netlogon' .EXAMPLE Get-PSService -ComputerName AD1, AD2 -Extended .EXAMPLE Get-PSService .EXAMPLE Get-PSService -Extended .NOTES General notes #> [cmdletBinding()] param( [alias('Computer', 'Computers')][string[]] $ComputerName = $Env:COMPUTERNAME, [ValidateSet('Default', 'Dcom', 'Wsman')][string] $Protocol = 'Default', [alias('Services')][string[]] $Service, [switch] $All, [switch] $Extended ) [string] $Class = 'win32_service' [string] $Properties = '*' if ($Service) { $CachedServices = @{} foreach ($S in $Service) { $CachedServices[$S] = $true } } $Information = Get-CimData -ComputerName $ComputerName -Protocol $Protocol -Class $Class -Properties $Properties if ($All) { if ($Service) { foreach ($Entry in $Information) { if ($CachedServices[$Entry.Name]) { $Entry } } } else { $Information } } else { foreach ($Data in $Information) { if ($Service) { if (-not $CachedServices[$Data.Name]) { continue } } $OutputService = [ordered] @{ ComputerName = if ($Data.PSComputerName) { $Data.PSComputerName } else { $Env:COMPUTERNAME } Status = $Data.State Name = $Data.Name ServiceType = $Data.ServiceType StartType = $Data.StartMode DisplayName = $Data.DisplayName } if ($Extended) { $OutputServiceExtended = [ordered] @{ StatusOther = $Data.Status ExitCode = $Data.ExitCode DesktopInteract = $Data.DesktopInteract ErrorControl = $Data.ErrorControl PathName = $Data.PathName Caption = $Data.Caption Description = $Data.Description Started = $Data.Started SystemName = $Data.SystemName AcceptPause = $Data.AcceptPause AcceptStop = $Data.AcceptStop ServiceSpecificExitCode = $Data.ServiceSpecificExitCode StartName = $Data.StartName TagId = $Data.TagId CheckPoint = $Data.CheckPoint DelayedAutoStart = $Data.DelayedAutoStart ProcessId = $Data.ProcessId WaitHint = $Data.WaitHint } [PSCustomObject] ($OutputService + $OutputServiceExtended) } else { [PSCustomObject] $OutputService } } } } function Set-ServiceRecovery { <# .SYNOPSIS Configures the recovery options for a specified Windows service. .DESCRIPTION This function sets the recovery options for a Windows service on a remote server. It allows you to define the actions to take upon service failure and the time intervals between these actions. .PARAMETER ServiceDisplayName The display name of the service for which recovery options need to be set. .PARAMETER Server The name of the server where the service is located. .PARAMETER action1 The action to take for the first failure. Default is "restart". .PARAMETER time1 The time interval (in milliseconds) before the first action is taken. Default is 30000 milliseconds. .PARAMETER action2 The action to take for the second failure. Default is "restart". .PARAMETER time2 The time interval (in milliseconds) before the second action is taken. Default is 30000 milliseconds. .PARAMETER actionLast The action to take for subsequent failures. Default is "restart". .PARAMETER timeLast The time interval (in milliseconds) before the subsequent action is taken. Default is 30000 milliseconds. .PARAMETER resetCounter The time interval (in seconds) after which the failure counter is reset. Default is 4000 seconds. .EXAMPLE Set-ServiceRecovery -ServiceDisplayName "Pulseway" -Server "MAIL1" Configures the recovery options for the "Pulseway" service on the server "MAIL1" with default settings. .NOTES For more information on service recovery options, refer to: https://technet.microsoft.com/en-us/library/cc742019.aspx #> [alias('Set-Recovery')] param ( [string] [Parameter(Mandatory = $true)] $ServiceDisplayName, [string] [Parameter(Mandatory = $true)] $Server, [string] $action1 = "restart", [int] $time1 = 30000, # in miliseconds [string] $action2 = "restart", [int] $time2 = 30000, # in miliseconds [string] $actionLast = "restart", [int] $timeLast = 30000, # in miliseconds [int] $resetCounter = 4000 # in seconds ) $serverPath = "\\" + $server $services = Get-CimInstance -ClassName 'Win32_Service' -ComputerName $Server | Where-Object { $_.DisplayName -imatch $ServiceDisplayName } $action = $action1 + "/" + $time1 + "/" + $action2 + "/" + $time2 + "/" + $actionLast + "/" + $timeLast foreach ($service in $services) { $output = sc.exe $serverPath failure $($service.Name) actions= $action reset= $resetCounter } } function Get-SqlQueryColumnInformation { <# .SYNOPSIS Retrieves column information for a specified table in a SQL database. .DESCRIPTION This function retrieves column information for a specified table in a SQL database using the INFORMATION_SCHEMA.COLUMNS view. .PARAMETER SqlServer The SQL Server instance where the database is located. .PARAMETER SqlDatabase The name of the SQL database. .PARAMETER Table The name of the table for which column information is to be retrieved. .EXAMPLE Get-SqlQueryColumnInformation -SqlServer "localhost" -SqlDatabase "MyDatabase" -Table "MyTable" Retrieves column information for the table "MyTable" in the database "MyDatabase" on the SQL Server instance "localhost". #> [CmdletBinding()] param ( [string] $SqlServer, [string] $SqlDatabase, [string] $Table ) $Table = $Table.Replace("dbo.", '').Replace('[', '').Replace(']', '') $SqlDatabase = $SqlDatabase.Replace('[', '').Replace(']', '') $SqlDatabase = "[$SqlDatabase]" $Query = "SELECT * FROM $SqlDatabase.INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$Table'" $SqlReturn = @( try { Invoke-DbaQuery -ErrorAction Stop -SqlInstance $SqlServer -Query $Query } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " "Error occured (Get-SqlQueryColumnInformation): $ErrorMessage" } ) return $SQLReturn } function New-SqlQuery { <# .SYNOPSIS Creates and executes SQL queries based on provided parameters. .DESCRIPTION The New-SqlQuery function generates SQL queries for inserting data into a specified table. It adds additional fields for tracking when the data was added and by whom. The function utilizes a mapping table to map object properties to table columns. .PARAMETER SqlSettings The SQL connection settings. .PARAMETER Object The object containing data to be inserted into the SQL table. .PARAMETER TableMapping A hashtable mapping object properties to table columns. .EXAMPLE $sqlSettings = Get-SqlSettings $object = [PSCustomObject]@{ DomainController = 'AD1.ad.evotec.xyz' Action = 'Event log automatic backup' BackupPath = 'C:\Windows\System32\Winevt\Logs\Archive-Security-2018-09-25-14-12-52-658.evtx' LogType = 'Security' Who = 'Automatic Backup' When = '2018-09-25 16:12:53' EventID = '1105' RecordID = '2434391' } $tableMapping = @{ DomainController = 'DomainController' Action = 'Action' BackupPath = 'BackupPath' LogType = 'LogType' Who = 'Who' When = 'When' EventID = 'EventID' RecordID = 'RecordID' } New-SqlQuery -SqlSettings $sqlSettings -Object $object -TableMapping $tableMapping #> [CmdletBinding()] param ( [Object] $SqlSettings, [Object] $Object, [Object] $TableMapping ) $ArraySQLQueries = New-ArrayList if ($null -ne $Object) { foreach ($O in $Object) { $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList $ArrayValues = New-ArrayList if (-not $O.AddedWhen) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWhen" -Value (Get-Date) -Force } if (-not $O.AddedWho) { Add-Member -InputObject $O -MemberType NoteProperty -Name "AddedWho" -Value ($Env:USERNAME) -Force } $DuplicateString = [System.Text.StringBuilder]::new() foreach ($E in $O.PSObject.Properties) { $FieldName = $E.Name $FieldValue = $E.Value foreach ($MapKey in $TableMapping.Keys) { if ($FieldName -eq $MapKey) { $MapValue = $TableMapping.$MapKey $MapValueSplit = $MapValue -Split ',' if ($FieldValue -is [DateTime]) { $FieldValue = Get-Date $FieldValue -Format "yyyy-MM-dd HH:mm:ss" } if ($FieldValue -like "*'*") { $FieldValue = $FieldValue -Replace "'", "''" } Add-ToArray -List $ArrayKeys -Element "[$($MapValueSplit[0])]" if ([string]::IsNullOrWhiteSpace($FieldValue)) { Add-ToArray -List $ArrayValues -Element "NULL" } else { foreach ($ColumnName in $SqlSettings.SqlCheckBeforeInsert) { $DuplicateColumn = $ColumnName.Replace("[", '').Replace("]", '') if ($MapValueSplit[0] -eq $DuplicateColumn) { if ($DuplicateString.Length -ne 0) { $null = $DuplicateString.Append(" AND ") } $null = $DuplicateString.Append("[$DuplicateColumn] = '$FieldValue'") } } Add-ToArray -List $ArrayValues -Element "'$FieldValue'" } } } } if ($ArrayKeys) { if ($null -ne $SqlSettings.SqlCheckBeforeInsert -and $DuplicateString.Length -gt 0) { Add-ToArray -List $ArrayMain -Element "IF NOT EXISTS (" Add-ToArray -List $ArrayMain -Element "SELECT 1 FROM " Add-ToArray -List $ArrayMain -Element "$($SqlSettings.SqlTable) " Add-ToArray -List $ArrayMain -Element "WHERE $($DuplicateString.ToString())" Add-ToArray -List $ArrayMain -Element ")" } Add-ToArray -List $ArrayMain -Element "BEGIN" Add-ToArray -List $ArrayMain -Element "INSERT INTO $($SqlSettings.SqlTable) (" Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ') VALUES (' Add-ToArray -List $ArrayMain -Element ($ArrayValues -join ',') Add-ToArray -List $ArrayMain -Element ')' Add-ToArray -List $ArrayMain -Element "END" Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } } } return $ArraySQLQueries } function New-SqlQueryAlterTable { <# .SYNOPSIS Creates SQL queries to add new columns to an existing table. .DESCRIPTION This function generates SQL queries to add new columns to an existing SQL table based on the provided TableMapping and ExistingColumns. .PARAMETER SqlSettings An object containing SQL connection settings. .PARAMETER TableMapping An object representing the mapping of new columns to be added. Keys are column names, values are column definitions. .PARAMETER ExistingColumns An array of existing column names in the table. .EXAMPLE $sqlSettings = Get-SqlSettings $tableMapping = @{ "NewColumn1" = "Column1Name, nvarchar(50)" "NewColumn2" = "Column2Name, int" } $existingColumns = @("Column1Name", "Column3Name") New-SqlQueryAlterTable -SqlSettings $sqlSettings -TableMapping $tableMapping -ExistingColumns $existingColumns # Generates SQL queries to add "NewColumn1" and "NewColumn2" to the table. #> [CmdletBinding()] param ( [Object]$SqlSettings, [Object]$TableMapping, [string[]] $ExistingColumns ) $ArraySQLQueries = New-ArrayList $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList foreach ($MapKey in $TableMapping.Keys) { $MapValue = $TableMapping.$MapKey $Field = $MapValue -Split ',' if ($ExistingColumns -notcontains $MapKey -and $ExistingColumns -notcontains $Field[0]) { if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" } } } if ($ArrayKeys) { Add-ToArray -List $ArrayMain -Element "ALTER TABLE $($SqlSettings.SqlTable) ADD" Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ';' Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } return $ArraySQLQueries } function New-SqlQueryCreateTable { <# .SYNOPSIS Creates SQL query to generate a new table based on provided table mapping. .DESCRIPTION This function generates a SQL query to create a new table in a database based on the table mapping provided. The table mapping should be a hashtable where the keys represent the column names and the values represent the column data types and constraints. .PARAMETER SqlSettings An object containing SQL connection settings. .PARAMETER TableMapping A hashtable containing the mapping of column names to data types and constraints. .EXAMPLE $sqlSettings = @{ SqlTable = "MyTable" } $tableMapping = @{ Column1 = "int", Column2 = "nvarchar(50) NULL", Column3 = "datetime NOT NULL" } New-SqlQueryCreateTable -SqlSettings $sqlSettings -TableMapping $tableMapping #> [CmdletBinding()] param ( [Object]$SqlSettings, [Object]$TableMapping ) $ArraySQLQueries = New-ArrayList $ArrayMain = New-ArrayList $ArrayKeys = New-ArrayList foreach ($MapKey in $TableMapping.Keys) { $MapValue = $TableMapping.$MapKey $Field = $MapValue -Split ',' if ($Field.Count -eq 1) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] [nvarchar](max) NULL" } elseif ($Field.Count -eq 2) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) NULL" } elseif ($Field.Count -eq 3) { Add-ToArray -List $ArrayKeys -Element "[$($Field[0])] $($Field[1]) $($Field[2])" } } if ($ArrayKeys) { Add-ToArray -List $ArrayMain -Element "CREATE TABLE $($SqlSettings.SqlTable) (" Add-ToArray -List $ArrayMain -Element "ID int IDENTITY(1,1) PRIMARY KEY," Add-ToArray -List $ArrayMain -Element ($ArrayKeys -join ',') Add-ToArray -List $ArrayMain -Element ')' Add-ToArray -List $ArraySQLQueries -Element ([string] ($ArrayMain) -replace "`n", "" -replace "`r", "") } return $ArraySQLQueries } function New-SqlTableMapping { <# .SYNOPSIS Creates a new SQL table mapping based on the provided parameters. .DESCRIPTION This function creates a new SQL table mapping based on the provided parameters. It generates a mapping for each property in the object based on its data type. .PARAMETER SqlTableMapping The existing SQL table mapping to update. .PARAMETER Object The object for which the SQL table mapping is being created. .PARAMETER Properties The properties of the object for which the SQL table mapping is being created. .PARAMETER BasedOnSqlTable Indicates whether the mapping should be based on an existing SQL table. .EXAMPLE $sqlTableMapping = New-SqlTableMapping -Object $myObject -Properties $myProperties Creates a new SQL table mapping for the object $myObject using the properties $myProperties. .EXAMPLE $sqlTableMapping = New-SqlTableMapping -SqlTableMapping $existingMapping -Object $myObject -Properties $myProperties -BasedOnSqlTable Updates the existing SQL table mapping $existingMapping based on the object $myObject and its properties $myProperties. #> [CmdletBinding()] param( [Object] $SqlTableMapping, [Object] $Object, $Properties, [switch] $BasedOnSqlTable ) if ($SqlTableMapping) { $TableMapping = $SqlTableMapping } else { $TableMapping = @{} if ($BasedOnSqlTable) { foreach ($Property in $Properties) { $FieldName = $Property $FieldNameSql = $Property $TableMapping.$FieldName = $FieldNameSQL } } else { foreach ($O in $Properties.HighestObject) { foreach ($Property in $Properties.Properties) { $FieldName = $Property $FieldValue = $O.$Property $FieldNameSQL = $FieldName.Replace(' ', '') if ($FieldValue -is [DateTime]) { $TableMapping.$FieldName = "$FieldNameSQL,[datetime],null" } elseif ($FieldValue -is [int] -or $FieldValue -is [Int64]) { $TableMapping.$FieldName = "$FieldNameSQL,[bigint]" } elseif ($FieldValue -is [bool]) { $TableMapping.$FieldName = "$FieldNameSQL,[bit]" } else { $TableMapping.$FieldName = "$FieldNameSQL" } } } } } return $TableMapping } function Send-SqlInsert { <# .SYNOPSIS Send data to a SQL table with optional table creation and alteration capabilities. .DESCRIPTION This function sends data to a specified SQL table. It provides options for table creation and alteration based on the provided settings. .PARAMETER Object Array of objects to be inserted into the SQL table. .PARAMETER SqlSettings Dictionary containing SQL server, database, and table information along with optional settings for table operations. .EXAMPLE Send-SqlInsert -Object $DataArray -SqlSettings $SqlConfig .NOTES General notes #> [CmdletBinding()] param( [Array] $Object, [System.Collections.IDictionary] $SqlSettings ) if ($SqlSettings.SqlTableTranspose) { $Object = Format-TransposeTable -Object $Object } $SqlTable = Get-SqlQueryColumnInformation -SqlServer $SqlSettings.SqlServer -SqlDatabase $SqlSettings.SqlDatabase -Table $SqlSettings.SqlTable $PropertiesFromAllObject = Get-ObjectPropertiesAdvanced -Object $Object -AddProperties 'AddedWhen', 'AddedWho' $PropertiesFromTable = $SqlTable.Column_name if ($null -eq $SqlTable) { if ($SqlSettings.SqlTableCreate) { Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, table creation is allowed, mapping will be done either on properties from object or from TableMapping defined in config" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject $CreateTableSQL = New-SqlQueryCreateTable -SqlSettings $SqlSettings -TableMapping $TableMapping } else { Write-Verbose "Send-SqlInsert - SqlTable doesn't exists, no table creation is allowed. Terminating" return "Error occured: SQL Table doesn't exists. SqlTableCreate option is disabled" } } else { if ($SqlSettings.SqlTableAlterIfNeeded) { if ( $SqlSettings.SqlTableMapping) { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, but SqlTableMapping is already defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject } else { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is allowed, and SqlTableMapping is not defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject $AlterTableSQL = New-SqlQueryAlterTable -SqlSettings $SqlSettings -TableMapping $TableMapping -ExistingColumns $SqlTable.Column_name } } else { if ( $SqlSettings.SqlTableMapping) { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is already defined" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromAllObject } else { Write-Verbose "Send-SqlInsert - Sql Table exists, Alter is not allowed, SqlTableMaping is not defined, using SqlTable Columns" $TableMapping = New-SqlTableMapping -SqlTableMapping $SqlSettings.SqlTableMapping -Object $Object -Properties $PropertiesFromTable -BasedOnSqlTable } } } $Queries = @( if ($CreateTableSQL) { foreach ($Sql in $CreateTableSQL) { $Sql } } if ($AlterTableSQL) { foreach ($Sql in $AlterTableSQL) { $Sql } } $SqlQueries = New-SqlQuery -Object $Object -SqlSettings $SqlSettings -TableMapping $TableMapping foreach ($Sql in $SqlQueries) { $Sql } ) $ReturnData = foreach ($Query in $Queries) { try { if ($Query) { $Query Invoke-DbaQuery -SqlInstance "$($SqlSettings.SqlServer)" -Database "$($SqlSettings.SqlDatabase)" -Query $Query -ErrorAction Stop } } catch { $ErrorMessage = $_.Exception.Message -replace "`n", " " -replace "`r", " " "Error occured (Send-SqlInsert): $ErrorMessage" } } return $ReturnData } function Find-TypesNeeded { <# .SYNOPSIS Finds if specific types are needed among the required types. .DESCRIPTION This function checks if any of the specified types in $TypesNeeded are among the required types in $TypesRequired. Returns $true if any type is found, otherwise $false. .PARAMETER TypesRequired Specifies an array of types that are required. .PARAMETER TypesNeeded Specifies an array of types to check if they are needed. .EXAMPLE Find-TypesNeeded -TypesRequired @('System.String', 'System.Int32') -TypesNeeded @('System.Int32') Checks if 'System.Int32' is among the required types 'System.String' and 'System.Int32'. .EXAMPLE Find-TypesNeeded -TypesRequired @('System.Management.Automation.PSCredential', 'System.Net.IPAddress') -TypesNeeded @('System.Net.IPAddress') Checks if 'System.Net.IPAddress' is needed among the required types 'System.Management.Automation.PSCredential' and 'System.Net.IPAddress'. #> [CmdletBinding()] param ( [Array] $TypesRequired, [Array] $TypesNeeded ) [bool] $Found = $False foreach ($Type in $TypesNeeded) { if ($TypesRequired -contains $Type) { $Found = $true break } } return $Found } Function Get-ModulesAvailability { <# .SYNOPSIS Checks the availability of a specified module and imports it if available. .DESCRIPTION This function checks if a specified module is available. If the module is not loaded, it attempts to import it. Returns $true if the module is successfully loaded, otherwise $false. .PARAMETER Name Specifies the name of the module to check and potentially import. .EXAMPLE Get-ModulesAvailability -Name "AzureRM" Checks if the "AzureRM" module is available and imports it if not already loaded. .EXAMPLE Get-ModulesAvailability -Name "ActiveDirectory" Checks the availability of the "ActiveDirectory" module and imports it if necessary. #> [cmdletBinding()] param( [string]$Name ) if (-not(Get-Module -Name $Name)) { if (Get-Module -ListAvailable | Where-Object { $_.Name -eq $Name }) { try { Import-Module -Name $Name return $true } catch { return $false } } else { return $false } } else { return $true } } function Search-Command { <# .SYNOPSIS Searches for a specific command by name. .DESCRIPTION This function checks if a command with the specified name exists in the current session. .PARAMETER CommandName Specifies the name of the command to search for. .EXAMPLE Search-Command -CommandName "Get-Process" Returns $true if the command "Get-Process" exists, otherwise $false. .EXAMPLE Search-Command -CommandName "UnknownCommand" Returns $false as "UnknownCommand" does not exist as a command. #> [cmdletbinding()] param ( [string] $CommandName ) return [bool](Get-Command -Name $CommandName -ErrorAction SilentlyContinue) } function Test-AvailabilityCommands { <# .SYNOPSIS Tests the availability of specified commands. .DESCRIPTION The Test-AvailabilityCommands function checks whether the specified commands are available in the current environment. .PARAMETER Commands Specifies an array of command names to test for availability. .EXAMPLE Test-AvailabilityCommands -Commands "Get-Process", "Get-Service" This example tests the availability of the "Get-Process" and "Get-Service" commands. .EXAMPLE Test-AvailabilityCommands -Commands "Get-Command", "Get-Help" This example tests the availability of the "Get-Command" and "Get-Help" commands. #> [cmdletBinding()] param ( [string[]] $Commands ) $CommandsStatus = foreach ($Command in $Commands) { $Exists = Get-Command -Name $Command -ErrorAction SilentlyContinue if ($Exists) { Write-Verbose "Test-AvailabilityCommands - Command $Command is available." } else { Write-Verbose "Test-AvailabilityCommands - Command $Command is not available." } $Exists } return $CommandsStatus } function Test-ComputerAvailability { <# .SYNOPSIS Tests the availability of specified servers using various methods. .DESCRIPTION This function tests the availability of specified servers by performing ping, WinRM, and port open tests. It provides detailed information about the availability status of each server. .PARAMETER Servers Specifies an array of server names to test. .PARAMETER Test Specifies the type of tests to perform. Valid values are 'All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen'. Default is 'All'. .PARAMETER Ports Specifies an array of TCP ports to test for port open. Default is 135. .PARAMETER PortsTimeout Specifies the timeout value (in milliseconds) for testing port open. Default is 100. .PARAMETER PingCount Specifies the number of ping attempts to make. Default is 1. .EXAMPLE Test-ComputerAvailability -Servers "Server1", "Server2" -Test Ping+WinRM Tests the availability of Server1 and Server2 using both ping and WinRM methods. .EXAMPLE Test-ComputerAvailability -Servers "Server3" -Test PortOpen -Ports 80,443 -PortsTimeout 200 Tests the availability of Server3 by checking if ports 80 and 443 are open within a timeout of 200 milliseconds. #> [CmdletBinding()] param( [string[]] $Servers, [ValidateSet('All', 'Ping', 'WinRM', 'PortOpen', 'Ping+WinRM', 'Ping+PortOpen', 'WinRM+PortOpen')] $Test = 'All', [int[]] $Ports = 135, [int] $PortsTimeout = 100, [int] $PingCount = 1 ) $OutputList = @( foreach ($Server in $Servers) { $Output = [ordered] @{ } $Output.ServerName = $Server if ($Test -eq 'All' -or $Test -like 'Ping*') { $Output.Pingable = Test-Connection -ComputerName $Server -Quiet -Count $PingCount } if ($Test -eq 'All' -or $Test -like '*WinRM*') { $Output.WinRM = (Test-WinRM -ComputerName $Server).Status } if ($Test -eq 'All' -or '*PortOpen*') { $Output.PortOpen = (Test-ComputerPort -Server $Server -PortTCP $Ports -Timeout $PortsTimeout).Status } [PSCustomObject] $Output } ) return $OutputList } function Test-ComputerPort { <# .SYNOPSIS Tests the connectivity of a computer on specified TCP and UDP ports. .DESCRIPTION The Test-ComputerPort function tests the connectivity of a computer on specified TCP and UDP ports. It checks if the specified ports are open and reachable on the target computer. .PARAMETER ComputerName Specifies the name of the computer to test the port connectivity. .PARAMETER PortTCP Specifies an array of TCP ports to test connectivity. .PARAMETER PortUDP Specifies an array of UDP ports to test connectivity. .PARAMETER Timeout Specifies the timeout value in milliseconds for the connection test. Default is 5000 milliseconds. .EXAMPLE Test-ComputerPort -ComputerName "Server01" -PortTCP 80,443 -PortUDP 53 -Timeout 3000 Tests the connectivity of Server01 on TCP ports 80 and 443, UDP port 53 with a timeout of 3000 milliseconds. .EXAMPLE Test-ComputerPort -ComputerName "Server02" -PortTCP 3389 -PortUDP 123 Tests the connectivity of Server02 on TCP port 3389, UDP port 123 with the default timeout of 5000 milliseconds. #> [CmdletBinding()] param ( [alias('Server')][string[]] $ComputerName, [int[]] $PortTCP, [int[]] $PortUDP, [int]$Timeout = 5000 ) begin { if ($Global:ProgressPreference -ne 'SilentlyContinue') { $TemporaryProgress = $Global:ProgressPreference $Global:ProgressPreference = 'SilentlyContinue' } } process { foreach ($Computer in $ComputerName) { foreach ($P in $PortTCP) { $Output = [ordered] @{ 'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'TCP' 'Status' = $null 'Summary' = $null 'Response' = $null } $TcpClient = Test-NetConnection -ComputerName $Computer -Port $P -InformationLevel Detailed -WarningAction SilentlyContinue if ($TcpClient.TcpTestSucceeded) { $Output['Status'] = $TcpClient.TcpTestSucceeded $Output['Summary'] = "TCP $P Successful" } else { $Output['Status'] = $false $Output['Summary'] = "TCP $P Failed" $Output['Response'] = $Warnings } [PSCustomObject]$Output } foreach ($P in $PortUDP) { $Output = [ordered] @{ 'ComputerName' = $Computer 'Port' = $P 'Protocol' = 'UDP' 'Status' = $null 'Summary' = $null } $UdpClient = [System.Net.Sockets.UdpClient]::new($Computer, $P) $UdpClient.Client.ReceiveTimeout = $Timeout $Encoding = [System.Text.ASCIIEncoding]::new() $byte = $Encoding.GetBytes("Evotec") [void]$UdpClient.Send($byte, $byte.length) $RemoteEndpoint = [System.Net.IPEndPoint]::new([System.Net.IPAddress]::Any, 0) try { $Bytes = $UdpClient.Receive([ref]$RemoteEndpoint) [string]$Data = $Encoding.GetString($Bytes) If ($Data) { $Output['Status'] = $true $Output['Summary'] = "UDP $P Successful" $Output['Response'] = $Data } } catch { $Output['Status'] = $false $Output['Summary'] = "UDP $P Failed" $Output['Response'] = $_.Exception.Message } $UdpClient.Close() $UdpClient.Dispose() [PSCustomObject]$Output } } } end { if ($TemporaryProgress) { $Global:ProgressPreference = $TemporaryProgress } } } function Test-ConfigurationCredentials { <# .SYNOPSIS Tests the configuration credentials for any null or empty values. .DESCRIPTION This function tests the configuration credentials provided to ensure that no keys have null or empty values. .PARAMETER Configuration The configuration object containing the credentials to be tested. .PARAMETER AllowEmptyKeys Specifies whether empty keys are allowed to be present in the configuration. .EXAMPLE Test-ConfigurationCredentials -Configuration $Config -AllowEmptyKeys $true Tests the configuration credentials in $Config allowing empty keys. .EXAMPLE Test-ConfigurationCredentials -Configuration $Config -AllowEmptyKeys $false Tests the configuration credentials in $Config without allowing empty keys. #> [CmdletBinding()] param ( [Object] $Configuration, $AllowEmptyKeys ) $Object = foreach ($Key in $Configuration.Keys) { if ($AllowEmptyKeys -notcontains $Key -and [string]::IsNullOrWhiteSpace($Configuration.$Key)) { Write-Verbose "Test-ConfigurationCredentials - Configuration $Key is Null or Empty! Terminating" @{ Status = $false; Output = $User.SamAccountName; Extended = "Credentials configuration $Key is Null or Empty!" } } } return $Object } function Test-ForestConnectivity { <# .SYNOPSIS Tests the connectivity to the Active Directory forest. .DESCRIPTION This function tests the connectivity to the Active Directory forest by attempting to retrieve the forest information. .EXAMPLE Test-ForestConnectivity Tests the connectivity to the Active Directory forest. #> [CmdletBinding()] param( ) Try { $null = Get-ADForest return $true } catch { return $False } } function Test-Key { <# .SYNOPSIS Checks if a specific key exists in a configuration table. .DESCRIPTION The Test-Key function checks if a specified key exists in a given configuration table. It returns true if the key exists, and false otherwise. .PARAMETER ConfigurationTable The configuration table to search for the key. .PARAMETER ConfigurationSection The section within the configuration table where the key is located. .PARAMETER ConfigurationKey The key to check for existence in the configuration table. .PARAMETER DisplayProgress Specifies whether to display progress messages. .EXAMPLE Test-Key -ConfigurationTable $configTable -ConfigurationSection "Section1" -ConfigurationKey "Key1" -DisplayProgress $true Checks if the key "Key1" exists in the "Section1" of the $configTable and displays a progress message. .EXAMPLE Test-Key -ConfigurationTable $configTable -ConfigurationKey "Key2" Checks if the key "Key2" exists in the $configTable without displaying progress messages. #> [CmdletBinding()] param( $ConfigurationTable, $ConfigurationSection = "", $ConfigurationKey, $DisplayProgress = $false ) if ($null -eq $ConfigurationTable) { return $false } try { $value = $ConfigurationTable.ContainsKey($ConfigurationKey) } catch { $value = $false } if ($value -eq $true) { if ($DisplayProgress -eq $true) { Write-Color @script:WriteParameters -Text "[i] ", "Parameter in configuration of ", "$ConfigurationSection.$ConfigurationKey", " exists." -Color White, White, Green, White } return $true } else { if ($DisplayProgress -eq $true) { Write-Color @script:WriteParameters -Text "[i] ", "Parameter in configuration of ", "$ConfigurationSection.$ConfigurationKey", " doesn't exist." -Color White, White, Red, White } return $false } } function Test-ModuleAvailability { <# .SYNOPSIS Tests the availability of required modules. .DESCRIPTION This function checks if the required modules are available for use. .EXAMPLE Test-ModuleAvailability Checks if the 'Get-AdForest' module is available. #> [CmdletBinding()] param( ) if (Search-Command -CommandName 'Get-AdForest') { } else { Write-Warning 'Modules required to run not found.' Exit } } function Test-WinRM { <# .SYNOPSIS Tests the WinRM connectivity on the specified computers. .DESCRIPTION The Test-WinRM function tests the WinRM connectivity on the specified computers and returns the status of the connection. .PARAMETER ComputerName Specifies the names of the computers to test WinRM connectivity on. .EXAMPLE Test-WinRM -ComputerName "Server01", "Server02" Tests the WinRM connectivity on Server01 and Server02. .EXAMPLE Test-WinRM -ComputerName "Server03" Tests the WinRM connectivity on Server03. #> [CmdletBinding()] param ( [alias('Server')][string[]] $ComputerName ) $Output = foreach ($Computer in $ComputerName) { $Test = [PSCustomObject] @{ Output = $null Status = $null ComputerName = $Computer } try { $Test.Output = Test-WSMan -ComputerName $Computer -ErrorAction Stop $Test.Status = $true } catch { $Test.Status = $false } $Test } $Output } function Get-TimeSettings { <# .SYNOPSIS Retrieves and displays time synchronization settings for the specified computer(s). .DESCRIPTION The Get-TimeSettings function retrieves and displays time synchronization settings for the specified computer(s). It provides information on the type of time synchronization mechanism being used, NTP server flags, cross-site synchronization flags, and announce flags. .PARAMETER ComputerName Specifies the computer(s) for which time synchronization settings are to be retrieved. If not specified, the local computer name is used. .PARAMETER Formatted Switch parameter to format the output in a structured manner. .PARAMETER Splitter Specifies the character used to split the output if Formatted parameter is used. .EXAMPLE Get-TimeSettings -ComputerName 'Server01' Retrieves time synchronization settings for a single computer named 'Server01'. .EXAMPLE Get-TimeSettings -ComputerName 'Server01','Server02' -Formatted -Splitter ',' Retrieves time synchronization settings for multiple computers named 'Server01' and 'Server02' in a formatted output separated by commas. #> [alias('Get-TimeSynchronization')] param( [string[]] $ComputerName, # [string] $Domain, [switch] $Formatted, [string] $Splitter ) $Types = @{ NT5DS = 'The time service synchronizes from the domain hierarchy.' NTP = 'The time service synchronizes from the servers specified in the NtpServer registry entry.' ALLSync = 'The time service uses all the available synchronization mechanisms.' NoSync = 'The time service does not synchronize with other sources.' } [flags()] enum NtpServerFlags { None = 0 SpecialInterval = 0x1 UseAsFallbackOnly = 0x2 SymmetricActive = 0x4 Client = 0x8 } $CrossSiteSyncFlags = @{ '0' = 'None' '1' = 'PdcOnly' '2' = 'All' } $AnnounceFlags = @{ '0' = 'Not a time server' '1' = 'Always time server' '2' = 'Automatic time server' '4' = 'Always reliable time server' '8' = 'Automatic reliable time server' '10' = 'The default value for domain members is 10. The default value for stand-alone clients and servers is 10.' } if ($null -eq $ComputerName) { $ComputerName = $env:COMPUTERNAME } foreach ($_ in $ComputerName) { [bool] $AppliedGPO = $false $TimeParameters = Get-PSRegistry -ComputerName $_ -RegistryPath "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\W32time\Parameters" if ($null -eq $TimeParameters.NtpServer) { $TimeParameters = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Parameters" $AppliedGPO = $true } $TimeConfig = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" $TimeNTPClient = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SOFTWARE\Policies\Microsoft\W32time\TimeProviders\NtpClient" if ($null -eq $TimeNTPClient.CrossSiteSyncFlags) { $TimeNTPClient = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NTPClient" } $TimeNTPServer = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NTPServer" $TimeVMProvider = Get-PSRegistry -ComputerName $_ -RegistryPath "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\VMICTimeProvider" $SecureTimeSeeding = Get-PSRegistry -ComputerName $_ -RegistryPath "HKLM\SYSTEM\CurrentControlSet\Services\W32Time\Config" -Key 'UtilizeSslTimeData' $NtpServers = $TimeParameters.NtpServer -split ' ' $Ntp = foreach ($NtpServer in $NtpServers) { $SplitNTP = $NtpServer -split ',' if ($SplitNTP.Count -eq 2) { if ($flagVal = $SplitNTP[1] -as [int]) { if ($flags = $flagVal -as [NtpServerFlags]) { $Intervals = $flags.ToString().Replace(', ', '+') } else { Write-Warning -Message "Get-TimeSettings - NtpServer flag value `"$flagVal`" could not be converted to NtpServerFlags enum" $Intervals = 'Incorrect' } } else { Write-Warning -Message "Get-TimeSettings - NtpServer flag value `"$($SplitNTP[1])`" could not be parsed as an integer" $Intervals = 'Incorrect' } } else { $Intervals = 'Missing' } [PSCustomObject] @{ NtpServer = $SplitNTP[0] Intervals = $Intervals } } if ($null -eq $TimeConfig.UtilizeSslTimeData) { $WSTSType = $true } elseif ($TimeConfig.UtilizeSslTimeData -eq 0) { $WSTSType = $false } else { $WSTSType = $true } if ($null -eq $SecureTimeSeeding.PSType) { $WSTSStatus = $false $WSTSType = $true } elseif ($SecureTimeSeeding.PSType -eq 'DWord' -and $SecureTimeSeeding.PSValue -eq 0) { $WSTSStatus = $false $WSTSType = $true } elseif ($SecureTimeSeeding.PSType -eq 'DWord' -and $SecureTimeSeeding.PSValue -eq 1) { $WSTSStatus = $true $WSTSType = $true } else { $WSTSStatus = $true $WSTSType = $false } [PSCustomObject] @{ ComputerName = $_ NtpServer = if ($Splitter) { $Ntp.NtpServer -join $Splitter } else { $Ntp.NtpServer } NtpServerIntervals = if ($Splitter) { $Ntp.Intervals -join $Splitter } else { $Ntp.Intervals } NtpType = $TimeParameters.Type NtpTypeComment = $Types["$($TimeParameters.Type)"] AppliedGPO = $AppliedGPO VMTimeProvider = [bool] $TimeVMProvider.Enabled WindowsSecureTimeSeeding = $WSTSStatus WindowsSecureTimeSeedingTypeCorrect = $WSTSType AnnounceFlags = $TimeConfig.AnnounceFlags AnnounceFlagsComment = $AnnounceFlags["$($TimeConfig.AnnounceFlags)"] NtpServerEnabled = [bool]$TimeNTPServer.Enabled NtpServerInputProvider = [bool]$TimeNTPServer.InputProvider MaxPosPhaseCorrection = $TimeConfig.MaxPosPhaseCorrection MaxnegPhaseCorrection = $TimeConfig.MaxnegPhaseCorrection MaxAllowedPhaseOffset = $TimeConfig.MaxAllowedPhaseOffset MaxPollInterval = $TimeConfig.MaxPollInterval MinPollInterval = $TimeConfig.MinPollInterval UpdateInterval = $TimeConfig.UpdateInterval ResolvePeerBackoffMinutes = $TimeNTPClient.ResolvePeerBackoffMinutes ResolvePeerBackoffMaxTimes = $TimeNTPClient.ResolvePeerBackoffMaxTimes SpecialPollInterval = $TimeNTPClient.SpecialPollInterval EventLogFlags = $TimeConfig.EventLogFlags NtpClientEnabled = [bool] $TimeNTPClient.Enabled NtpClientCrossSiteSyncFlags = $CrossSiteSyncFlags["$($TimeNTPClient.CrossSiteSyncFlags)"] NtpClientInputProvider = [bool] $TimeNTPClient.InputProvider TimeNTPClient = $TimeNTPClient.SpecialPollInterval } } } function Get-TimeZoneAdvanced { <# .SYNOPSIS Retrieves the time zone information for the specified computer(s). .DESCRIPTION This function retrieves the time zone information for the specified computer(s) including the computer name, time zone caption, and current local time. .PARAMETER ComputerName Specifies the name(s) of the computer(s) to retrieve the time zone information from. Default is the local computer. .PARAMETER Credential Specifies the credentials to use for accessing remote computers. .EXAMPLE Get-TimeZoneAdvanced # Retrieves time zone information for the local computer. .EXAMPLE Get-TimeZoneAdvanced -ComputerName "Server01", "Server02" -Credential $cred # Retrieves time zone information for Server01 and Server02 using specified credentials. #> param( [string[]]$ComputerName = $Env:COMPUTERNAME, [System.Management.Automation.PSCredential] $Credential = [System.Management.Automation.PSCredential]::Empty ) foreach ($computer in $computerName) { $TimeZone = Get-WmiObject -Class win32_timezone -ComputerName $computer -Credential $Credential $LocalTime = Get-WmiObject -Class win32_localtime -ComputerName $computer -Credential $Credential $Output = @{ 'ComputerName' = $localTime.__SERVER; 'TimeZone' = $timeZone.Caption; 'CurrentTime' = (Get-Date -Day $localTime.Day -Month $localTime.Month); } $Object = New-Object -TypeName PSObject -Property $Output Write-Output $Object } } function Get-TimeZoneLegacy () { <# .SYNOPSIS Retrieves the standard name of the current time zone. .DESCRIPTION The Get-TimeZoneLegacy function retrieves the standard name of the current time zone using the legacy method. .EXAMPLE Get-TimeZoneLegacy # Output: "Pacific Standard Time" #> return ([System.TimeZone]::CurrentTimeZone).StandardName } function Measure-Collection { <# .SYNOPSIS Measures the execution time of a script block and outputs the duration. .DESCRIPTION This function measures the time taken to execute a given script block and outputs the duration in days, hours, minutes, seconds, milliseconds, and ticks. .PARAMETER Name Specifies the name of the measurement. .PARAMETER ScriptBlock Specifies the script block to be executed and measured. .EXAMPLE Measure-Collection -Name "Example" -ScriptBlock { Start-Sleep -Seconds 5 } # Outputs: Name: Example, 0 days, 0 hours, 0 minutes, 5 seconds, 0 milliseconds, ticks 5000000 .EXAMPLE Measure-Collection -Name "Another Example" -ScriptBlock { Get-Process } # Outputs: Name: Another Example, 0 days, 0 hours, 0 minutes, X seconds, Y milliseconds, ticks Z #> param( [string] $Name, [ScriptBlock] $ScriptBlock ) $Time = [System.Diagnostics.Stopwatch]::StartNew() Invoke-Command -ScriptBlock $ScriptBlock $Time.Stop() "Name: $Name, $($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds, ticks $($Time.Elapsed.Ticks)" } function Set-TimeSynchronization { <# .SYNOPSIS Configures time synchronization settings on the local machine. .DESCRIPTION This function sets up time synchronization on the local machine by configuring the time source, server type, NTP settings, and time correction parameters. .PARAMETER TimeSource Specifies the time source to synchronize with. Default is 'time.windows.com'. .PARAMETER MaxPosPhaseCorrection Specifies the maximum positive time correction in seconds. Default is 86400 seconds (24 hours). .PARAMETER MaxnegPhaseCorrection Specifies the maximum negative time correction in seconds. Default is 86400 seconds (24 hours). .PARAMETER PollInterval Specifies the poll interval in seconds. Default is 1800 seconds (30 minutes). .EXAMPLE Set-TimeSynchronization -TimeSource 'time.windows.com' -MaxPosPhaseCorrection 86400 -MaxnegPhaseCorrection 86400 -PollInterval 1800 Configures time synchronization using default settings. .EXAMPLE Set-TimeSynchronization -TimeSource 'pool.ntp.org' -MaxPosPhaseCorrection 43200 -MaxnegPhaseCorrection 43200 -PollInterval 3600 Configures time synchronization with a different time source and shorter time correction limits. #> param( [string[]] $TimeSource = 'time.windows.com', [int] $MaxPosPhaseCorrection = 86400, [int] $MaxnegPhaseCorrection = 86400, [int] $PollInterval = 1800 ) Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters -Name Type -Value 'NTP' Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name AnnounceFlags -Value 5 Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer -Name Enabled -Value 1 Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Parameters -Name NtpServer -Value "$TimeSource,0x1" Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpClient -Name SpecialPollInterval -Value $PollInterval Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name MaxPosPhaseCorrection -Value $MaxPosPhaseCorrection Set-ItemProperty -Path HKLM:\SYSTEM\CurrentControlSet\Services\W32Time\Config -Name MaxnegPhaseCorrection -Value $MaxnegPhaseCorrection Stop-Service -Name W32Time Start-Service -Name W32Time } function Start-TimeLog { <# .SYNOPSIS Starts a new stopwatch for logging time. .DESCRIPTION This function starts a new stopwatch that can be used for logging time durations. .EXAMPLE Start-TimeLog Starts a new stopwatch for logging time. #> [CmdletBinding()] param() [System.Diagnostics.Stopwatch]::StartNew() } function Stop-TimeLog { <# .SYNOPSIS Stops the stopwatch and returns the elapsed time in a specified format. .DESCRIPTION The Stop-TimeLog function stops the provided stopwatch and returns the elapsed time in a specified format. The function can output the elapsed time as a single string or an array of days, hours, minutes, seconds, and milliseconds. .PARAMETER Time Specifies the stopwatch object to stop and retrieve the elapsed time from. .PARAMETER Option Specifies the format in which the elapsed time should be returned. Valid values are 'OneLiner' (default) or 'Array'. .PARAMETER Continue Indicates whether the stopwatch should continue running after retrieving the elapsed time. .EXAMPLE $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() # Perform some operations Stop-TimeLog -Time $stopwatch # Output: "0 days, 0 hours, 0 minutes, 5 seconds, 123 milliseconds" .EXAMPLE $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() # Perform some operations Stop-TimeLog -Time $stopwatch -Option Array # Output: ["0 days", "0 hours", "0 minutes", "5 seconds", "123 milliseconds"] #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)][System.Diagnostics.Stopwatch] $Time, [ValidateSet('OneLiner', 'Array')][string] $Option = 'OneLiner', [switch] $Continue ) Begin { } Process { if ($Option -eq 'Array') { $TimeToExecute = "$($Time.Elapsed.Days) days", "$($Time.Elapsed.Hours) hours", "$($Time.Elapsed.Minutes) minutes", "$($Time.Elapsed.Seconds) seconds", "$($Time.Elapsed.Milliseconds) milliseconds" } else { $TimeToExecute = "$($Time.Elapsed.Days) days, $($Time.Elapsed.Hours) hours, $($Time.Elapsed.Minutes) minutes, $($Time.Elapsed.Seconds) seconds, $($Time.Elapsed.Milliseconds) milliseconds" } } End { if (-not $Continue) { $Time.Stop() } return $TimeToExecute } } function Show-Array { <# .SYNOPSIS Displays the elements of an ArrayList with optional type information. .DESCRIPTION The Show-Array function displays each element of the provided ArrayList. Optionally, it can also show the type of each element. .PARAMETER List Specifies the ArrayList containing the elements to display. .PARAMETER WithType Switch parameter to include type information along with each element. .EXAMPLE $myList = New-Object System.Collections.ArrayList $myList.Add("Apple") $myList.Add(42) Show-Array -List $myList # Output: # Apple # 42 .EXAMPLE $myList = New-Object System.Collections.ArrayList $myList.Add("Banana") $myList.Add(3.14) Show-Array -List $myList -WithType # Output: # Banana (Type: String) # 3.14 (Type: Double) #> [CmdletBinding()] param( [System.Collections.ArrayList] $List, [switch] $WithType ) foreach ($Element in $List) { $Type = Get-ObjectType -Object $Element if ($WithType) { Write-Output "$Element (Type: $($Type.ObjectTypeName))" } else { Write-Output $Element } } } function Show-DataInVerbose { <# .SYNOPSIS Displays the properties of an object in a verbose manner. .DESCRIPTION This function takes an object as input and displays each property of the object in a verbose format. .PARAMETER Object Specifies the object whose properties will be displayed. .EXAMPLE $data = [PSCustomObject]@{ Name = "John Doe" Age = 30 City = "New York" } Show-DataInVerbose -Object $data Description: Displays the properties of the $data object in a verbose manner. #> [CmdletBinding()] param( [Object] $Object ) foreach ($O in $Object) { foreach ($E in $O.PSObject.Properties) { $FieldName = $E.Name $FieldValue = $E.Value Write-Verbose "Display-DataInVerbose - FieldName: $FieldName FieldValue: $FieldValue" } } } function Show-TableVisualization { <# .SYNOPSIS Displays a table visualization of the input object. .DESCRIPTION The Show-TableVisualization function displays a table visualization of the input object using Format-Table. It also provides additional information about the table data. .PARAMETER Object Specifies the input object to be visualized as a table. .EXAMPLE PS C:\> Get-Process | Show-TableVisualization Displays a table visualization of the processes retrieved by Get-Process. .EXAMPLE PS C:\> $data = Get-Service | Where-Object { $_.Status -eq 'Running' } | Select-Object Name, DisplayName, Status PS C:\> $data | Show-TableVisualization Displays a table visualization of the selected service data. #> [CmdletBinding()] param ( [parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] $Object ) if ($Color) { Write-Color "[i] This is how table looks like in Format-Table" -Color Yellow } Write-Verbose '[i] This is how table looks like in Format-Table' $Object | Format-Table -AutoSize $Data = Format-PSTable $Object Write-Verbose "[i] Rows Count $($Data.Count) Column Count $($Data[0].Count)" $RowNr = 0 if ($Color) { Write-Color "[i] Presenting table after conversion" -Color Yellow } foreach ($Row in $Data) { $ColumnNr = 0 foreach ($Column in $Row) { Write-Verbose "Row: $RowNr Column: $ColumnNr Data: $Column" $ColumnNr++ } $RowNr++ } } function Save-XML { <# .SYNOPSIS Saves an XML document to a specified file path. .DESCRIPTION This function saves an XML document to a specified file path using UTF-8 encoding without BOM. .PARAMETER FilePath Specifies the path where the XML document will be saved. .PARAMETER xml Specifies the XML document to be saved. .EXAMPLE Save-XML -FilePath "C:\Documents\example.xml" -xml $xmlDocument Saves the XML document $xmlDocument to the file "example.xml" located in the "C:\Documents" directory. #> param ( [string] $FilePath, [System.Xml.XmlNode] $xml ) $utf8WithoutBom = New-Object System.Text.UTF8Encoding($false) $writer = New-Object System.IO.StreamWriter($FilePath, $false, $utf8WithoutBom) $xml.Save( $writer ) $writer.Close() } function Set-XML { <# .SYNOPSIS Sets a specific node value in an XML file. .DESCRIPTION This function sets the value of a specified node in an XML file at the given path. .PARAMETER FilePath The path to the XML file. .PARAMETER Paths An array of paths to navigate through the XML structure. .PARAMETER Node The name of the node to set the value for. .PARAMETER Value The value to set for the specified node. .EXAMPLE Set-XML -FilePath "C:\example.xml" -Paths "Root", "Child" -Node "Value" -Value "NewValue" Sets the value of the "Value" node under "Root/Child" path in the XML file to "NewValue". .NOTES File encoding is assumed to be UTF-8. #> param ( [string] $FilePath, [string[]]$Paths, [string] $Node, [string] $Value ) [xml]$xmlDocument = Get-Content -Path $FilePath -Encoding UTF8 $XmlElement = $xmlDocument foreach ($Path in $Paths) { $XmlElement = $XmlElement.$Path } $XmlElement.$Node = $Value $xmlDocument.Save($FilePath) } function Get-ProtocolDefaults { <# .SYNOPSIS Gets a list of default settings for SSL/TLS protocols .DESCRIPTION Gets a list of default settings for SSL/TLS protocols .PARAMETER WindowsVersion Windows Version to search for .PARAMETER AsList If true, returns a list of protocol names for all Windows Versions, otherwise returns a single entry for the specified Windows Version .EXAMPLE Get-ProtocolDefaults -AsList | Format-Table .EXAMPLE Get-ProtocolDefaults -WindowsVersion 'Windows 10 1809' | Format-Table .NOTES Based on: https://docs.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp- According to this https://github.com/MicrosoftDocs/windowsserverdocs/issues/2783 SCHANNEL service requires direct enablement so the list is kind of half useful #> [cmdletbinding(DefaultParameterSetName = 'WindowsVersion')] param( [Parameter(Mandatory, ParameterSetName = 'WindowsVersion')][string] $WindowsVersion, [Parameter(Mandatory, ParameterSetName = 'AsList')][switch] $AsList ) $Defaults = [ordered] @{ 'Windows Server 2022' = [ordered] @{ 'Version' = 'Windows Server 2022' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Enabled' 'TLS13Server' = 'Enabled' } 'Windows Server 2019 20H2' = [ordered] @{ 'Version' = 'Windows Server 2019 20H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2019 2004' = [ordered] @{ 'Version' = 'Windows Server 2019 2004' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2019 1909' = [ordered] @{ 'Version' = 'Windows Server 2019 1909' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2019 1903" = [ordered] @{ 'Version' = 'Windows Server 2019 1903' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2019 1809" = [ordered] @{ 'Version' = 'Windows Server 2019 1809' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2016 1803" = [ordered] @{ 'Version' = 'Windows Server 2016 1803' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows Server 2016 1607" = [ordered] @{ 'Version' = 'Windows Server 2019 1607' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2012 R2' = [ordered] @{ 'Version' = 'Windows Server 2012 R2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2012' = [ordered] @{ 'Version' = 'Windows Server 2012' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2008 R2' = [ordered] @{ 'Version' = 'Windows Server 2008 R2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Enabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Disabled' 'TLS11Server' = 'Disabled' 'TLS12Client' = 'Disabled' 'TLS12Server' = 'Disabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows Server 2008' = [ordered] @{ 'Version' = 'Windows Server 2008' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Enabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Disabled' 'TLS11Server' = 'Disabled' 'TLS12Client' = 'Disabled' 'TLS12Server' = 'Disabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 11 21H2' = [ordered] @{ 'Version' = 'Windows 11 21H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Enabled' 'TLS13Server' = 'Enabled' } 'Windows 10 21H1' = [ordered] @{ 'Version' = 'Windows 10 21H1' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 20H2' = [ordered] @{ 'Version' = 'Windows 10 20H2' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 2004' = [ordered] @{ 'Version' = 'Windows 10 2004' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } 'Windows 10 Insider Preview' = [ordered] @{ 'Version' = 'Windows 10 Insider Preview' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1909" = [ordered] @{ 'Version' = 'Windows 10 1909' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1903" = [ordered] @{ 'Version' = 'Windows 10 1903' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1809" = [ordered] @{ 'Version' = 'Windows 10 1809' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1803" = [ordered] @{ 'Version' = 'Windows 10 1803' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1709" = [ordered] @{ 'Version' = 'Windows 10 1709' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1703" = [ordered] @{ 'Version' = 'Windows 10 1703' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1607" = [ordered] @{ 'Version' = 'Windows 10 1607' 'PCT10' = 'Not supported' 'SSL2Client' = 'Not supported' 'SSL2Server' = 'Not supported' 'SSL3Client' = 'Disabled' 'SSL3Server' = 'Disabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1511" = [ordered] @{ 'Version' = 'Windows 10 1511' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } "Windows 10 1507" = [ordered] @{ 'Version' = 'Windows 10 1507' 'PCT10' = 'Not supported' 'SSL2Client' = 'Disabled' 'SSL2Server' = 'Disabled' 'SSL3Client' = 'Enabled' 'SSL3Server' = 'Enabled' 'TLS10Client' = 'Enabled' 'TLS10Server' = 'Enabled' 'TLS11Client' = 'Enabled' 'TLS11Server' = 'Enabled' 'TLS12Client' = 'Enabled' 'TLS12Server' = 'Enabled' 'TLS13Client' = 'Not supported' 'TLS13Server' = 'Not supported' } } if ($AsList) { foreach ($Key in $Defaults.Keys) { [PSCustomObject] $Defaults[$Key] } } else { if ($Defaults[$WindowsVersion]) { $Defaults[$WindowsVersion] } else { [ordered] @{ 'Version' = 'Unknown' 'PCT10' = 'Unknown' 'SSL2Client' = 'Unknown' 'SSL2Server' = 'Unknown' 'SSL3Client' = 'Unknown' 'SSL3Server' = 'Unknown' 'TLS10Client' = 'Unknown' 'TLS10Server' = 'Unknown' 'TLS11Client' = 'Unknown' 'TLS11Server' = 'Unknown' 'TLS12Client' = 'Unknown' 'TLS12Server' = 'Unknown' 'TLS13Client' = 'Unknown' 'TLS13Server' = 'Unknown' } } } } Add-Type -TypeDefinition @" public enum RGBColors { None, Black, Navy, DarkBlue, MediumBlue, Blue, DarkGreen, Green, Teal, DarkCyan, DeepSkyBlue, DarkTurquoise, MediumSpringGreen, Lime, SpringGreen, Aqua, Cyan, MidnightBlue, DodgerBlue, LightSeaGreen, ForestGreen, SeaGreen, DarkSlateGray, DarkSlateGrey, LimeGreen, MediumSeaGreen, Turquoise, RoyalBlue, SteelBlue, DarkSlateBlue, MediumTurquoise, Indigo, DarkOliveGreen, CadetBlue, CornflowerBlue, MediumAquamarine, DimGray, DimGrey, SlateBlue, OliveDrab, SlateGray, SlateGrey, LightSlateGray, LightSlateGrey, MediumSlateBlue, LawnGreen, Chartreuse, Aquamarine, Maroon, Purple, Olive, Grey, Gray, //Grey, SkyBlue, LightSkyBlue, BlueViolet, DarkRed, DarkMagenta, SaddleBrown, DarkSeaGreen, LightGreen, MediumPurple, DarkViolet, PaleGreen, DarkOrchid, YellowGreen, Sienna, Brown, DarkGray, DarkGrey, LightBlue, GreenYellow, PaleTurquoise, LightSteelBlue, PowderBlue, FireBrick, DarkGoldenrod, MediumOrchid, RosyBrown, DarkKhaki, Silver, MediumVioletRed, IndianRed, Peru, Chocolate, Tan, LightGray, LightGrey, Thistle, Orchid, Goldenrod, PaleVioletRed, Crimson, Gainsboro, Plum, BurlyWood, LightCyan, Lavender, DarkSalmon, Violet, PaleGoldenrod, LightCoral, Khaki, AliceBlue, Honeydew, Azure, SandyBrown, Wheat, Beige, WhiteSmoke, MintCream, GhostWhite, Salmon, AntiqueWhite, Linen, LightGoldenrodYellow, OldLace, Red, Fuchsia, Magenta, DeepPink, OrangeRed, Tomato, HotPink, Coral, DarkOrange, LightSalmon, Orange, LightPink, Pink, Gold, PeachPuff, NavajoWhite, Moccasin, Bisque, MistyRose, BlanchedAlmond, PapayaWhip, LavenderBlush, Seashell, Cornsilk, LemonChiffon, FloralWhite, Snow, Yellow, LightYellow, Ivory, White } "@ Export-ModuleMember -Function @('Add-ToArray', 'Add-ToArrayAdvanced', 'Add-ToHashTable', 'Add-WinADUserGroups', 'Clear-DataInformation', 'Compare-MultipleObjects', 'Compare-ObjectProperties', 'Compare-ObjectsAdvanced', 'Convert-ADGuidToSchema', 'Convert-ADSchemaToGuid', 'Convert-BinaryToHex', 'Convert-BinaryToString', 'Convert-Color', 'Convert-CountryCodeToCountry', 'Convert-CountryToContinent', 'Convert-CountryToCountryCode', 'Convert-DomainFqdnToNetBIOS', 'Convert-DomainToSid', 'Convert-ExchangeEmail', 'Convert-ExchangeItems', 'Convert-ExchangeRecipient', 'Convert-ExchangeSize', 'Convert-HexToBinary', 'Convert-Identity', 'Convert-IpAddressToPtr', 'Convert-KeyToKeyValue', 'Convert-Office365License', 'Convert-Size', 'Convert-TimeToDays', 'Convert-ToDateTime', 'Convert-ToTimeSpan', 'Convert-TwoArraysIntoOne', 'Convert-UAC', 'Convert-UserAccountControl', 'ConvertFrom-Color', 'ConvertFrom-DistinguishedName', 'ConvertFrom-ErrorRecord', 'ConvertFrom-LanguageCode', 'ConvertFrom-NetbiosName', 'ConvertFrom-ObjectToString', 'ConvertFrom-OperationType', 'ConvertFrom-ScriptBlock', 'ConvertFrom-SID', 'ConvertFrom-X500Address', 'ConvertTo-DistinguishedName', 'ConvertTo-FlatHashtable', 'ConvertTo-FlatObject', 'ConvertTo-Identity', 'ConvertTo-ImmutableID', 'ConvertTo-JsonLiteral', 'ConvertTo-NormalizedString', 'ConvertTo-OperatingSystem', 'ConvertTo-OrderedDictionary', 'ConvertTo-PrettyObject', 'ConvertTo-SID', 'Copy-Dictionary', 'Copy-DictionaryManual', 'Dismount-PSRegistryPath', 'Find-ADConnectServer', 'Find-DatesCurrentDayMinusDayX', 'Find-DatesCurrentDayMinuxDaysX', 'Find-DatesCurrentHour', 'Find-DatesDayPrevious', 'Find-DatesDayToday', 'Find-DatesMonthCurrent', 'Find-DatesMonthPast', 'Find-DatesPastHour', 'Find-DatesPastWeek', 'Find-DatesQuarterCurrent', 'Find-DatesQuarterLast', 'Find-ExchangeServer', 'Find-HyperVServer', 'Find-MyProgramData', 'Find-ServerTypes', 'Find-TypesNeeded', 'Find-UsersProxyAddressesStatus', 'Format-Dictionary', 'Format-FirstXChars', 'Format-PSTable', 'Format-Stream', 'Format-StringToSentence', 'Format-ToTitleCase', 'Format-TransposeTable', 'Format-View', 'Get-ADADministrativeGroups', 'Get-ADEncryptionTypes', 'Get-ADTrustAttributes', 'Get-CimData', 'Get-Colors', 'Get-Computer', 'Get-ComputerApplication', 'Get-ComputerBios', 'Get-ComputerCPU', 'Get-ComputerCulture', 'Get-ComputerDevice', 'Get-ComputerDisk', 'Get-ComputerDiskLogical', 'Get-ComputerFirewall', 'Get-ComputerInstalledUpdates', 'Get-ComputerMemory', 'Get-ComputerMissingDrivers', 'Get-ComputerNetFramework', 'Get-ComputerNetwork', 'Get-ComputerOemInformation', 'Get-ComputerOperatingSystem', 'Get-ComputerRAM', 'Get-ComputerRDP', 'Get-ComputerRoles', 'Get-ComputerService', 'Get-ComputerSMB', 'Get-ComputerSMBShare', 'Get-ComputerSMBShareList', 'Get-ComputerSMBSharePermissions', 'Get-ComputerStartup', 'Get-ComputerSystem', 'Get-ComputerTask', 'Get-ComputerTime', 'Get-ComputerTimeNtp', 'Get-ComputerWindowsFeatures', 'Get-ComputerWindowsUpdates', 'Get-DataInformation', 'Get-FileEncoding', 'Get-FileInformation', 'Get-FileMetaData', 'Get-FileName', 'Get-FileOwner', 'Get-FilePermission', 'Get-FilesInFolder', 'Get-FileSize', 'Get-GitHubLatestRelease', 'Get-GitHubVersion', 'Get-HashMaxValue', 'Get-HTML', 'Get-IPAddressInformation', 'Get-IPAddressRangeInformation', 'Get-Logger', 'Get-MimeType', 'Get-ModulesAvailability', 'Get-MyIpAddress', 'Get-ObjectCount', 'Get-ObjectData', 'Get-ObjectEnumValues', 'Get-ObjectKeys', 'Get-ObjectProperties', 'Get-ObjectPropertiesAdvanced', 'Get-ObjectTitles', 'Get-ObjectType', 'Get-OperatingSystem', 'Get-PathSeparator', 'Get-PathTemporary', 'Get-ProtocolDefaults', 'Get-PSRegistry', 'Get-PSService', 'Get-RandomCharacters', 'Get-RandomFileName', 'Get-RandomPassword', 'Get-RandomStringName', 'Get-SqlQueryColumnInformation', 'Get-TemporaryDirectory', 'Get-TimeSettings', 'Get-TimeZoneAdvanced', 'Get-TimeZoneLegacy', 'Get-Types', 'Get-WinADDSAGuid', 'Get-WinADForestControllers', 'Get-WinADForestDetails', 'Get-WinADForestOptions', 'Get-WinADOrganizationalUnitData', 'Get-WinADOrganizationalUnitFromDN', 'Get-WinADUsersByDN', 'Get-WinADUsersByOU', 'Get-WinADUserSnapshot', 'Initialize-ModulePortable', 'Invoke-CommandCustom', 'Join-Uri', 'Join-UriQuery', 'Measure-Collection', 'Merge-Array', 'Merge-Objects', 'Mount-PSRegistryPath', 'New-ArrayList', 'New-GenericList', 'New-PSRegistry', 'New-Runspace', 'New-SqlQuery', 'New-SqlQueryAlterTable', 'New-SqlQueryCreateTable', 'New-SqlTableMapping', 'Remove-DuplicateObjects', 'Remove-EmptyValue', 'Remove-FilePermission', 'Remove-FromArray', 'Remove-ObjectsExistingInTarget', 'Remove-PSRegistry', 'Remove-WhiteSpace', 'Remove-WinADUserGroups', 'Rename-LatinCharacters', 'Rename-UserValuesFromHash', 'Save-XML', 'Search-Command', 'Select-Properties', 'Send-Email', 'Send-SqlInsert', 'Set-DnsServerIpAddress', 'Set-EmailBody', 'Set-EmailBodyPreparedTable', 'Set-EmailBodyReplacement', 'Set-EmailBodyReplacementTable', 'Set-EmailFormatting', 'Set-EmailHead', 'Set-EmailReportBranding', 'Set-EmailWordReplacements', 'Set-EmailWordReplacementsHash', 'Set-FileInheritance', 'Set-FileOwner', 'Set-FilePermission', 'Set-PasswordRemotely', 'Set-PSRegistry', 'Set-ServiceRecovery', 'Set-TimeSynchronization', 'Set-WinADGroupSynchronization', 'Set-WinADUserFields', 'Set-WinADUserSettingGAL', 'Set-WinADUserStatus', 'Set-XML', 'Show-Array', 'Show-DataInVerbose', 'Show-TableVisualization', 'Split-Array', 'Start-InternalFunction', 'Start-MyProgram', 'Start-Runspace', 'Start-TimeLog', 'Stop-Runspace', 'Stop-TimeLog', 'Test-AvailabilityCommands', 'Test-ComputerAvailability', 'Test-ComputerPort', 'Test-ConfigurationCredentials', 'Test-ForestConnectivity', 'Test-IsDistinguishedName', 'Test-Key', 'Test-ModuleAvailability', 'Test-PSRegistry', 'Test-WinRM') -Alias @('Add-ADUserGroups', 'Convert-ExchangeRecipientDetails', 'Convert-FromColor', 'Copy-Hashtable', 'Copy-OrderedHashtable', 'Dismount-RegistryPath', 'Find-ADSyncServer', 'Format-AddSpaceToSentence', 'Format-Debug', 'Format-ListStream', 'Format-TableStream', 'Format-Verbose', 'Format-Warning', 'FS', 'FV', 'Get-ADUserSnapshot', 'Get-ComputerApplications', 'Get-ComputerNetworkCard', 'Get-ComputerServices', 'Get-ComputerTasks', 'Get-FilePermissions', 'Get-MyIP', 'Get-PSPermissions', 'Get-RDPSecurity', 'Get-ServerRoles', 'Get-TimeSynchronization', 'Get-WinADDomainControllers', 'Get-WinADDomainGUIDs', 'Get-WinADForestGUIDs', 'Join-Url', 'Join-UrlQuery', 'Mount-RegistryPath', 'Remove-ADUserGroups', 'Remove-EmptyValues', 'Remove-StringLatinCharacters', 'Set-ADUserName', 'Set-ADUserSettingGAL', 'Set-ADUserStatus', 'Set-EmailBodyTableReplacement', 'Set-Recovery', 'Sort-Dictionary', 'Test-IsDN') # SIG # Begin signature block # MIItqwYJKoZIhvcNAQcCoIItnDCCLZgCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDu7JNDoz1PwtqF # bT6WQoLbN8ng5i4UDY8BDZ1sRLObv6CCJq4wggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggWQMIIDeKADAgECAhAFmxtXno4hMuI5B72nd3VcMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8MCIw # aTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauyefLK # EdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34LzB4Tm # dDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+xembu # d8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhAkHnD # eMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1LyuGwN1 # XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2PVld # QnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37AlLTS # YW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD76GSm # M9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/ybzT # QRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXAj6Kx # fgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD # VR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzANBgkq # hkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNkaA9Wz3eucPn9mkqZucl4 # XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjSPMFDQK4dUPVS/JA7u5iZ # aWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK7VB6fWIhCoDIc2bRoAVg # X+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eBcg3AFDLvMFkuruBx8lbk # apdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp5aPNoiBB19GcZNnqJqGL # FNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msgdDDS4Dk0EIUhFQEI6FUy # 3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vriRbgjU2wGb2dVf0a1TD9u # KFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ79ARj6e/CVABRoIoqyc54 # zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5nLGbsQAe79APT0JsyQq8 # 7kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3i0objwG2J5VT6LaJbVu8 # aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0HEEcRrYc9B9F1vM/zZn4w # ggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 # c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqG # SIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbS # g9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9 # /UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXn # HwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0 # VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4f # sbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40Nj # gHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0 # QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvv # mz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T # /jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk # 42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5r # mQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5n # P+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcG # CCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQu # Y29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8v # Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNV # HSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIB # AH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxp # wc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIl # zpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQ # cAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfe # Kuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+j # Sbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJsh # IUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6 # OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDw # N7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR # 81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2 # VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIGsDCCBJigAwIBAgIQ # CK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJVUzEV # MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t # MSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjEwNDI5MDAw # MDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5WRuxiEL1M4zrPYGXcMW7xIUmMJ+k # jmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJPDqFX/IiZwZHMgQM+TXAkZLON4gh9 # NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXzENOLsvsI8IrgnQnAZaf6mIBJNYc9 # URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bqHPNlaJGiTUyCEUhSaN4QvRRXXegY # E2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTCfMjqGzLmysL0p6MDDnSlrzm2q2AS # 4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaDG7dqZy3SvUQakhCBj7A7CdfHmzJa # wv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urOkfW+0/tvk2E0XLyTRSiDNipmKF+w # c86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7ADK5GyNnm+960IHnWmZcy740hQ83eR # Gv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4R+Z1MI3sMJN2FKZbS110YU0/EpF2 # 3r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlNWdt4z4FKPkBHX8mBUHOFECMhWWCK # ZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0IU0F8WD1Hs/q27IwyCQLMbDwMVhEC # AwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFGg34Ou2 # O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9P # MA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDAzB3BggrBgEFBQcB # AQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggr # BgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 # c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwHAYDVR0gBBUwEzAH # BgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcNAQEMBQADggIBADojRD2NCHbuj7w6 # mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcTEp6QRJ9L/Z6jfCbVN7w6XUhtldU/ # SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WTauPrINHVUHmImoqKwba9oUgYftzY # gBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9ntSZz0rdKOtfJqGVWEjVGv7XJz/9 # kNF2ht0csGBc8w2o7uCJob054ThO2m67Np375SFTWsPK6Wrxoj7bQ7gzyE84FJKZ # 9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0HKKlS43Nb3Y3LIU/Gs4m6Ri+kAew # Q3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL6TEa/y4ZXDlx4b6cpwoG1iZnt5Lm # Tl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+16oh7cGvmoLr9Oj9FpsToFpFSi0HA # SIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8M4+uKIw8y4+ICw2/O/TOHnuO77Xr # y7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrFhsP2JjMMB0ug0wcCampAMEhLNKhR # ILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy1lKQ/a+FSCH5Vzu0nAPthkX0tGFu # v2jiJmCG6sivqf6UHedjGzqGVnhOMIIGvDCCBKSgAwIBAgIQC65mvFq6f5WHxvnp # BOMzBDANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5 # NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTI0MDkyNjAwMDAwMFoXDTM1MTEy # NTIzNTk1OVowQjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSAwHgYD # VQQDExdEaWdpQ2VydCBUaW1lc3RhbXAgMjAyNDCCAiIwDQYJKoZIhvcNAQEBBQAD # ggIPADCCAgoCggIBAL5qc5/2lSGrljC6W23mWaO16P2RHxjEiDtqmeOlwf0KMCBD # Er4IxHRGd7+L660x5XltSVhhK64zi9CeC9B6lUdXM0s71EOcRe8+CEJp+3R2O8oo # 76EO7o5tLuslxdr9Qq82aKcpA9O//X6QE+AcaU/byaCagLD/GLoUb35SfWHh43rO # H3bpLEx7pZ7avVnpUVmPvkxT8c2a2yC0WMp8hMu60tZR0ChaV76Nhnj37DEYTX9R # eNZ8hIOYe4jl7/r419CvEYVIrH6sN00yx49boUuumF9i2T8UuKGn9966fR5X6kgX # j3o5WHhHVO+NBikDO0mlUh902wS/Eeh8F/UFaRp1z5SnROHwSJ+QQRZ1fisD8UTV # DSupWJNstVkiqLq+ISTdEjJKGjVfIcsgA4l9cbk8Smlzddh4EfvFrpVNnes4c16J # idj5XiPVdsn5n10jxmGpxoMc6iPkoaDhi6JjHd5ibfdp5uzIXp4P0wXkgNs+CO/C # acBqU0R4k+8h6gYldp4FCMgrXdKWfM4N0u25OEAuEa3JyidxW48jwBqIJqImd93N # Rxvd1aepSeNeREXAu2xUDEW8aqzFQDYmr9ZONuc2MhTMizchNULpUEoA6Vva7b1X # CB+1rxvbKmLqfY/M/SdV6mwWTyeVy5Z/JkvMFpnQy5wR14GJcv6dQ4aEKOX5AgMB # AAGjggGLMIIBhzAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUB # Af8EDDAKBggrBgEFBQcDCDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1s # BwEwHwYDVR0jBBgwFoAUuhbZbU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFJ9X # LAN3DigVkGalY17uT5IfdqBbMFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1l # U3RhbXBpbmdDQS5jcmwwgZAGCCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhho # dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNl # cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZU # aW1lU3RhbXBpbmdDQS5jcnQwDQYJKoZIhvcNAQELBQADggIBAD2tHh92mVvjOIQS # R9lDkfYR25tOCB3RKE/P09x7gUsmXqt40ouRl3lj+8QioVYq3igpwrPvBmZdrlWB # b0HvqT00nFSXgmUrDKNSQqGTdpjHsPy+LaalTW0qVjvUBhcHzBMutB6HzeledbDC # zFzUy34VarPnvIWrqVogK0qM8gJhh/+qDEAIdO/KkYesLyTVOoJ4eTq7gj9UFAL1 # UruJKlTnCVaM2UeUUW/8z3fvjxhN6hdT98Vr2FYlCS7Mbb4Hv5swO+aAXxWUm3Wp # ByXtgVQxiBlTVYzqfLDbe9PpBKDBfk+rabTFDZXoUke7zPgtd7/fvWTlCs30VAGE # sshJmLbJ6ZbQ/xll/HjO9JbNVekBv2Tgem+mLptR7yIrpaidRJXrI+UzB6vAlk/8 # a1u7cIqV0yef4uaZFORNekUgQHTqddmsPCEIYQP7xGxZBIhdmm4bhYsVA6G2WgNF # YagLDBzpmk9104WQzYuVNsxyoVLObhx3RugaEGru+SojW4dHPoWrUhftNpFC5H7Q # EY7MhKRyrBe7ucykW7eaCuWBsBb4HOKRFVDcrZgdwaSIqMDiCLg4D+TPVgKx2EgE # deoHNHT9l3ZDBD+XgbF+23/zBjeCtxz+dL/9NWR6P2eZRi7zcEO1xwcdcqJsyz/J # ceENc2Sg8h3KeFUCS7tpFk7CrDqkMIIHXzCCBUegAwIBAgIQB8JSdCgUotar/iTq # F+XdLjANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln # aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBT # aWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExMB4XDTIzMDQxNjAwMDAwMFoX # DTI2MDcwNjIzNTk1OVowZzELMAkGA1UEBhMCUEwxEjAQBgNVBAcMCU1pa2/FgsOz # dzEhMB8GA1UECgwYUHJ6ZW15c8WCYXcgS8WCeXMgRVZPVEVDMSEwHwYDVQQDDBhQ # cnplbXlzxYJhdyBLxYJ5cyBFVk9URUMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQCUmgeXMQtIaKaSkKvbAt8GFZJ1ywOH8SwxlTus4McyrWmVOrRBVRQA # 8ApF9FaeobwmkZxvkxQTFLHKm+8knwomEUslca8CqSOI0YwELv5EwTVEh0C/Daeh # vxo6tkmNPF9/SP1KC3c0l1vO+M7vdNVGKQIQrhxq7EG0iezBZOAiukNdGVXRYOLn # 47V3qL5PwG/ou2alJ/vifIDad81qFb+QkUh02Jo24SMjWdKDytdrMXi0235CN4Rr # W+8gjfRJ+fKKjgMImbuceCsi9Iv1a66bUc9anAemObT4mF5U/yQBgAuAo3+jVB8w # iUd87kUQO0zJCF8vq2YrVOz8OJmMX8ggIsEEUZ3CZKD0hVc3dm7cWSAw8/FNzGNP # lAaIxzXX9qeD0EgaCLRkItA3t3eQW+IAXyS/9ZnnpFUoDvQGbK+Q4/bP0ib98XLf # QpxVGRu0cCV0Ng77DIkRF+IyR1PcwVAq+OzVU3vKeo25v/rntiXCmCxiW4oHYO28 # eSQ/eIAcnii+3uKDNZrI15P7VxDrkUIc6FtiSvOhwc3AzY+vEfivUkFKRqwvSSr4 # fCrrkk7z2Qe72Zwlw2EDRVHyy0fUVGO9QMuh6E3RwnJL96ip0alcmhKABGoIqSW0 # 5nXdCUbkXmhPCTT5naQDuZ1UkAXbZPShKjbPwzdXP2b8I9nQ89VSgQIDAQABo4IC # AzCCAf8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYE # FHrxaiVZuDJxxEk15bLoMuFI5233MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK # BggrBgEFBQcDAzCBtQYDVR0fBIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEz # ODQyMDIxQ0ExLmNybDBToFGgT4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0Rp # Z2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5j # cmwwPgYDVR0gBDcwNTAzBgZngQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3 # dy5kaWdpY2VydC5jb20vQ1BTMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcw # AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8v # Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmlu # Z1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB # CwUAA4ICAQC3EeHXUPhpe31K2DL43Hfh6qkvBHyR1RlD9lVIklcRCR50ZHzoWs6E # BlTFyohvkpclVCuRdQW33tS6vtKPOucpDDv4wsA+6zkJYI8fHouW6Tqa1W47YSrc # 5AOShIcJ9+NpNbKNGih3doSlcio2mUKCX5I/ZrzJBkQpJ0kYha/pUST2CbE3JroJ # f2vQWGUiI+J3LdiPNHmhO1l+zaQkSxv0cVDETMfQGZKKRVESZ6Fg61b0djvQSx51 # 0MdbxtKMjvS3ZtAytqnQHk1ipP+Rg+M5lFHrSkUlnpGa+f3nuQhxDb7N9E8hUVev # xALTrFifg8zhslVRH5/Df/CxlMKXC7op30/AyQsOQxHW1uNx3tG1DMgizpwBasrx # h6wa7iaA+Lp07q1I92eLhrYbtw3xC2vNIGdMdN7nd76yMIjdYnAn7r38wwtaJ3KY # D0QTl77EB8u/5cCs3ShZdDdyg4K7NoJl8iEHrbqtooAHOMLiJpiL2i9Yn8kQMB6/ # Q6RMO3IUPLuycB9o6DNiwQHf6Jt5oW7P09k5NxxBEmksxwNbmZvNQ65Zn3exUAKq # G+x31Egz5IZ4U/jPzRalElEIpS0rgrVg8R8pEOhd95mEzp5WERKFyXhe6nB6bSYH # v8clLAV0iMku308rpfjMiQkqS3LLzfUJ5OHqtKKQNMLxz9z185UCszGCBlMwggZP # AgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEw # PwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2 # IFNIQTM4NCAyMDIxIENBMQIQB8JSdCgUotar/iTqF+XdLjANBglghkgBZQMEAgEF # AKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgor # BgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3 # DQEJBDEiBCBN4JvsBhNrWwtfuPvyNRsV1rWCXK+i4TfHF3nciowNmjANBgkqhkiG # 9w0BAQEFAASCAgBsaaSJeq07m3jocis7JaT/q/nXjHJojTtULoz1nIJqUdkuz5D7 # OH81cJeM7NcMDMEBsRB+SL7s7M5jUzFbSNl9Y5VuuJz0ZsKrgV+Yq1Kl9a7y6Uz2 # 6wM94ZWS21Wq8q3bYXoJWh5lANpPjMJowh9p6gU3xhwBXxCZI0RgSx0GrOjYmQod # 8L5v+GgWOkzqns5JxmrzCdFuY1yxkIu/EudV8u2XveTNGaulMHLaOu6G+xIe+eY+ # HL3cYKg1htZ1Loz37frxDMTDviygxctrVZas3JLmxE5/Ykpru/junISEXfQvDu5L # StNeWRxgyFz3QYCt9tTEyPQVDMNvS6FDVZOn2t5TFzIA/PBXToRF+dQ7hKnTs8dP # qIWbQV94WOu1z4f4QAu3eghqsc4U7iNGs1rIvFpkv77ClnkQpAF/dL+FziQK720c # IN0OyGWaFlAqSorNVU4ZTErZQYm6//Z/usL+NAAC0UF91dEmqCjQo7DBwgVD1jku # At9XmNBdDXedHxBF+sd/NjraJo5diDfSqmd07A4POpFZOGHxZBnhSRKVrjuQqQ0L # lcV6SrFHzqlplkRQE8+Wv3Jy+UCsYr2aIMlWTPiIGAv227mqJhv62eS6FFBI9s7o # lh628uBBZDwpjHGoXG3YApJRCQARpCCon86Rq2HxmE9S7E4GHf2ovy80eaGCAyAw # ggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYD # VQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBH # NCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0ECEAuuZrxaun+Vh8b56QTj # MwQwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwG # CSqGSIb3DQEJBTEPFw0yNDEwMjYwNzQyMjhaMC8GCSqGSIb3DQEJBDEiBCAeoGFs # pEVmuguifyzmiFA+WN7gggpjo82pZylNjoPN5jANBgkqhkiG9w0BAQEFAASCAgA2 # osViknDXVS4BQ9M2ucYi3hAq1FcNFqAYB0iGBFFr2QFIAvinh/qSONoby2Fmdrz0 # HKuEwSpMADJZs/DMzbgMyxC31eyrkuooYzt4zzXb4B9U80M4LUilEqnnxEof1JIw # lW4V5pv2qBYRjKYDVuAk1JXS1MzQUWg1Pn+NCZyEpEYfWzC9PgMexgda97wWtJRy # avCJAhysZHWNNItnpIfvGKonwuXO4QEe1N5s5QTw/ixA5TS6s9cI+vf5cU0ZmEsC # 7KThVdkWRIy7/GBrNLudU5E8ykxp8S4wCghCh4ore1GhsFd7of/i41aQNvHnHTV5 # O6Y+2p84TQ7aGtFZirhDdIoXLR2esXu4XAtJVIX16VgvVQUCBHDFhxuYCNkQQARH # nCfJsJKwRPHbTR5bl0ZIgoCUunW7s/K5SvNuSItTPLqov/29qQi2UsJZMhUeC04u # GzsonflRu3VC3i5M/JRyz7Rz3sw2BxnPMQQ7wfYqEfwW8pp4/bacy/j0IT2VYLhz # QrM5LnT2BmfRB1mQ3dJEmAepZWLez2Oa1PWWXsmLHPl11Q/AROZeww2/jBAGaH5s # NwIzshKCITihzO6r+Xjnueu4z5qYSbd52qLHc9iXLxCUw6pRI9rYoS9R1MJVHMjH # FsWtSk4x3K0PhDt3tcEDqod7ilWMxqbhz3b68yJE8A== # SIG # End signature block |