├── .gitattributes ├── .gitignore ├── Add-ClusterMsmqRole.ps1 ├── ConvertTo-ZabbixJson.ps1 ├── Get-SpecialFolderPath.ps1 ├── Get-SvnAuthor.ps1 ├── Get-TerminologyTranslation.ps1 ├── Import-Component.ps1 ├── LICENSE ├── New-DynamicParameter.ps1 ├── New-GitSvnAuthorsFile.ps1 ├── README.md ├── Remove-ComObject.ps1 ├── Split-CommandLine.ps1 ├── Start-ConsoleProcess.ps1 ├── Step-Dictionary.ps1 ├── Use-Object.ps1 ├── Use-ServiceAccount.ps1 └── Write-Host.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .wip 2 | -------------------------------------------------------------------------------- /Add-ClusterMsmqRole.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Creates clustered MSMQ role. 4 | 5 | .Description 6 | Creates clustered MSMQ role with correct group type and dependencies. 7 | Can optionally add services to the created group. 8 | 9 | .Parameter Name 10 | Role name. 11 | 12 | .Parameter Disk 13 | Cluster disk name to use for shared storage. 14 | Must exist and be available. 15 | 16 | .Parameter StaticAddress 17 | Array of IP addresses that should be added to the group. 18 | 19 | .Parameter Services 20 | Array of Windows services that should be added to the group. 21 | 22 | .Parameter Cluster 23 | Cluster name to operate on. Netbios/FQDN. 24 | If the input for this parameter is omitted, then the all operations are run on the local cluster. 25 | 26 | .Parameter Network 27 | Cluster network name. 28 | If the input for this parameter is omitted, first available network will be used. 29 | 30 | .Parameter Start 31 | Start role group after it's been created. 32 | 33 | .Example 34 | Add-ClusterMsmqRole -Name 'MSMQ' -Disk 'Cluster Disk 1' -StaticAddress '10.20.30.40' -Start 35 | 36 | Create new MSMQ role with network name 'MSMQ' and IP address '10.20.30.40' using 'Cluster Disk 1' for shared storage. 37 | Start 'MSMQ' group after it's been created. 38 | 39 | .Example 40 | Add-ClusterMsmqRole -Name 'MSMQ' -Disk 'Cluster Disk 1' -StaticAddress '10.20.30.40' -Service 'SomeService' 41 | 42 | Create new MSMQ role with network name 'MSMQ' and IP address '10.20.30.40' using 'Cluster Disk 1' for shared storage. 43 | Add windows service 'SomeService' to 'MSMQ' group. Do not start 'MSMQ' group. 44 | #> 45 | function Add-ClusterMsmqRole { 46 | [CmdletBinding()] 47 | Param ( 48 | [Parameter(Mandatory = $true, Position = 0)] 49 | [ValidateNotNullOrEmpty()] 50 | [string]$Name, 51 | 52 | [Parameter(Mandatory = $true, Position = 1)] 53 | [ValidateNotNullOrEmpty()] 54 | [string]$Disk, 55 | 56 | [Parameter(Mandatory = $true, Position = 2)] 57 | [ValidateNotNullOrEmpty()] 58 | [ipaddress[]]$StaticAddress, 59 | 60 | [Parameter(Position = 3)] 61 | [ValidateNotNullOrEmpty()] 62 | [string[]]$Service, 63 | 64 | [Parameter(Position = 4)] 65 | [ValidateScript({ 66 | Get-Cluster -Name $_ 67 | })] 68 | [string]$Cluster = '.', 69 | 70 | [Parameter(Position = 5)] 71 | [string]$Network, 72 | 73 | [Parameter(Position = 6)] 74 | [switch]$Start 75 | ) 76 | 77 | Begin { 78 | # Set default cluster name 79 | $PSDefaultParameterValues = @{ 80 | '*-Cluster*:Cluster' = $Cluster 81 | } 82 | } 83 | 84 | End { 85 | # Using $Network = if ($Network){...} breaks pipeline somehow 86 | if ($Network) { 87 | [Microsoft.FailoverClusters.PowerShell.ClusterNetwork]$Network = Get-ClusterNetwork -Name $Network 88 | } else { 89 | # Using Select-Object stops pipeline too... 90 | [Microsoft.FailoverClusters.PowerShell.ClusterNetwork]$Network = @(Get-ClusterNetwork)[0] 91 | } 92 | Write-Verbose "[*] Cluster network: $Network" 93 | 94 | [Microsoft.FailoverClusters.PowerShell.ClusterResource]$Disk = Get-ClusterResource -Name $Disk | 95 | Where-Object { 96 | $_.ResourceType -eq 'Physical Disk' -and 97 | $_.OwnerGroup -eq 'Available Storage' 98 | } 99 | 100 | if (-not $Disk) { 101 | throw "Cluster disk '$Disk' is not available!" 102 | } 103 | 104 | Write-Verbose "[*] Creating group: $Name" 105 | $Group = Add-ClusterGroup -Name $Name -GroupType Msmq 106 | 107 | $StaticAddressList = foreach ($ip in $StaticAddress) { 108 | $ipName = "IP Address $ip" 109 | Write-Verbose "[*] Adding '$ipName' to '$Group' using '$Network'" 110 | $Group | 111 | Add-ClusterResource -Name $ipName -ResourceType 'IP Address' | 112 | Set-ClusterParameter -Multiple @{ 113 | Network = $Network.Name 114 | Address = "$ip" 115 | SubnetMask = $Network.AddressMask 116 | } | Write-Verbose 117 | } 118 | 119 | Write-Verbose "[*] Moving cluster disk '$Disk' to group '$Group'" 120 | $Disk | Move-ClusterResource -Group $Group.Name | Write-Verbose 121 | 122 | Write-Verbose "[*] Adding '$Name (Network Name)' to group '$Group'" 123 | $NetworkName = $Group | Add-ClusterResource -Name $Name -ResourceType 'Network Name' 124 | 125 | Write-Verbose "[*] Setting 'DnsName' on '$NetworkName (Network Name)'" 126 | $NetworkName | Set-ClusterParameter -Name 'DnsName' -Value $Name | Write-Verbose 127 | 128 | $NetworkNameDep = ( 129 | $StaticAddressList | ForEach-Object {"([$_])"} 130 | ) -join ' and ' 131 | 132 | Write-Verbose "[*] Adding dependencies to '$NetworkName (Network Name)': $NetworkNameDep" 133 | $NetworkName | Set-ClusterResourceDependency -Dependency $NetworkNameDep | Write-Verbose 134 | 135 | $MsmqName = "$Name-MSMQ" 136 | Write-Verbose "[*] Adding resource of type 'Msmq' to group '$Group': $MsmqName" 137 | $Msmq = $Group | Add-ClusterResource -Name $MsmqName -ResourceType Msmq 138 | 139 | $MsmqDep = "([$Disk]) and ([$NetworkName])" 140 | Write-Verbose "[*] Adding dependencies to '$Msmq (MSMQ)': $MsmqDep" 141 | $Msmq | Set-ClusterResourceDependency -Dependency $MsmqDep | Write-Verbose 142 | 143 | foreach ($svc in $Service) { 144 | Write-Verbose "[*] Adding resource of type 'Generic Service' to group '$Group': $svc" 145 | $Group | 146 | Add-ClusterResource -Name $svc -ResourceType 'Generic Service' | 147 | Set-ClusterParameter -Name 'ServiceName' -Value $svc | Write-Verbose 148 | } 149 | 150 | if ($Start) { 151 | Write-Verbose "[*] Starting group: $Group" 152 | $Group | Start-ClusterGroup | Write-Verbose 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /ConvertTo-ZabbixJson.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Convert object to JSON supported by Zabbix low-level discovery. 4 | 5 | .Description 6 | Convert object to JSON supported by Zabbix low-level discovery. 7 | Object property names will be converted to uppercase, prefixed by # 8 | and wrapped in curly braces: Name -> {#NAME} 9 | 10 | .Parameter InputObject 11 | Object to be converted. You can supply multiple objects. 12 | 13 | .Parameter Compress 14 | Omits white space and indented formatting in the output string. 15 | If not set, the ConvertTo-Json defaults are used. 16 | 17 | .Example 18 | [pscustomobject]@{a=1 ; b = 2}, @{c = 3 ; d = 4} | ConvertTo-ZabbixJson 19 | 20 | Converts PSCustomObject and hashtable to Zabbix LLD JSON: 21 | 22 | { 23 | "data": [ 24 | { 25 | "{#A}": 1, 26 | "{#B}": 2 27 | }, 28 | { 29 | "{#D}": 4, 30 | "{#C}": 3 31 | } 32 | ] 33 | } 34 | 35 | .Example 36 | Get-Website | Select-Object name, physicalPath | ConvertTo-ZabbixJson 37 | 38 | Converts website object to Zabbix LLD JSON: 39 | 40 | { 41 | "data": [ 42 | { 43 | "{#NAME}": "Default Web Site", 44 | "{#PHYSICALPATH}": "C:\\inetpub\\wwwroot" 45 | } 46 | ] 47 | } 48 | #> 49 | function ConvertTo-ZabbixJson { 50 | [CmdletBinding()] 51 | Param ( 52 | [Parameter(ValueFromPipeline = $true, Position = 0)] 53 | $InputObject, 54 | 55 | [Parameter(Position = 1)] 56 | [switch]$Compress 57 | ) 58 | 59 | Begin { 60 | $Result = New-Object -TypeName System.Collections.Generic.List[PSCustomObject] 61 | } 62 | 63 | Process { 64 | foreach ($item in $InputObject) { 65 | # if item is hashtable, convert it to PSCustomObject 66 | $item = [pscustomobject]$item 67 | 68 | if ($InvalidPropertyName = @($item.PsObject.Properties.Name) -match '[^0-9A-Z_\.]') { 69 | throw "Invalid property name: $InvalidPropertyName . Allowed symbols for LLD macro names are: 0-9 , A-Z , _ , ." 70 | } 71 | 72 | # Build properties for Zabbix LLD object 73 | foreach ($property in $item.PsObject.Properties.Name) { 74 | [void]$Result.Add( 75 | @{ 76 | "{#$($property.ToUpperInvariant())}" = $item.$property.ToString() 77 | } 78 | ) 79 | } 80 | } 81 | } 82 | 83 | End { 84 | if ($Result.Count) { 85 | @{data = $Result} | ConvertTo-Json -Compress:$Compress 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /Get-SpecialFolderPath.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Gets the path to the system special folder that is identified by the specified enumeration (CSIDL, KNOWNFOLDERID or best match). 4 | 5 | .Description 6 | Gets the path to the system special folder that is identified by the specified enumeration. On pre .NET 4.0 systems tries to map 7 | unknown KNOWNFOLDERID to CSIDLs. This, for example allows to query for 'ProgramFilesx86' directory when PowerShell is running 8 | in .Net 3.5, where SpecialFolder enumeration contains only KNOWNFOLDERID for 'ProgramFiles'. 9 | 10 | .Parameter Name 11 | An array of strings (SpecialFolder, CSIDL or both) to query. Accepts pipeline input. 12 | 13 | .Parameter Csidl 14 | An array of strings containing CSIDLs to query. 15 | 16 | .Parameter KnownFolderId 17 | An array of strings containing KNOWNFOLDERIDs to query. 18 | 19 | .Example 20 | 'Favorites' | Get-SpecialFolderPath 21 | 22 | Get folder path for 'Favorites' folder. 23 | 24 | .Example 25 | Get-SpecialFolderPath -Name 'Favorites' 26 | 27 | Get folder path for 'Favorites' folder. 28 | 29 | .Example 30 | 'Favorites', 'CSIDL_DESKTOP' | Get-SpecialFolderPath 31 | 32 | Get folder paths for 'Favorites' and Desktop folders 33 | 34 | .Example 35 | Get-SpecialFolderPath -Name 'Favorites', 'CSIDL_DESKTOP' 36 | 37 | Get folder paths for 'Favorites' and Desktop folders 38 | 39 | .Example 40 | Get-SpecialFolderPath -Csidl 'CSIDL_DESKTOP' 41 | 42 | Get folder path for Desktop folder by CSIDL 43 | 44 | .Example 45 | Get-SpecialFolderPath -KnownFolderId 'Favorites' 46 | 47 | Get folder path for 'Favorites' folder by KNOWNFOLDERID 48 | 49 | .Example 50 | Get-SpecialFolderPath -Name 'NetHood' 51 | 52 | Get folder path for 'NetHood' KnownFolderId. On my system, there is no 'NetHood' KNOWNFOLDERID in SpecialFolder enumeration, 53 | so the function will fallback to CSIDL mapping. Example of verbose output in this situation: 54 | 55 | VERBOSE: Checking if [System.Environment]::GetFolderPath available 56 | VERBOSE: Result: True 57 | VERBOSE: No KnownFolderId for: 'NetHood', trying to map to CSIDL 58 | VERBOSE: KnownFolderId 'NetHood' is mapped to CSIDL(s): CSIDL_NETHOOD 59 | VERBOSE: Registering type: Shell32.Tools 60 | VERBOSE: Processing CSIDL(s): CSIDL_NETHOOD 61 | C:\Users\beatcracker\AppData\Roaming\Microsoft\Windows\Network Shortcuts 62 | #> 63 | function Get-SpecialFolderPath 64 | { 65 | [CmdletBinding()] 66 | Param() 67 | DynamicParam 68 | { 69 | # CSIDLs are taken from the latest Shlobj.h (Win10 SDK) 70 | # https://gist.github.com/beatcracker/4b154d46cc26776b50e7/raw/a317160dad57157f100e0f6e6d68c692c2bee7f1/ShlObj.h 71 | $CsidlEnum = @{ 72 | CSIDL_ADMINTOOLS = 0x0030 # \Start Menu\Programs\Administrative Tools 73 | CSIDL_ALTSTARTUP = 0x001d # non localized startup 74 | CSIDL_APPDATA = 0x001a # \Application Data 75 | CSIDL_BITBUCKET = 0x000a # \Recycle Bin 76 | CSIDL_CDBURN_AREA = 0x003b # USERPROFILE\Local Settings\Application Data\Microsoft\CD Burning 77 | CSIDL_COMMON_ADMINTOOLS = 0x002f # All Users\Start Menu\Programs\Administrative Tools 78 | CSIDL_COMMON_ALTSTARTUP = 0x001e # non localized common startup 79 | CSIDL_COMMON_APPDATA = 0x0023 # All Users\Application Data 80 | CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019 # All Users\Desktop 81 | CSIDL_COMMON_DOCUMENTS = 0x002e # All Users\Documents 82 | CSIDL_COMMON_FAVORITES = 0x001f 83 | CSIDL_COMMON_MUSIC = 0x0035 # All Users\My Music 84 | CSIDL_COMMON_OEM_LINKS = 0x003a # Links to All Users OEM specific apps 85 | CSIDL_COMMON_PICTURES = 0x0036 # All Users\My Pictures 86 | CSIDL_COMMON_PROGRAMS = 0X0017 # All Users\Start Menu\Programs 87 | CSIDL_COMMON_STARTMENU = 0x0016 # All Users\Start Menu 88 | CSIDL_COMMON_STARTUP = 0x0018 # All Users\Startup 89 | CSIDL_COMMON_TEMPLATES = 0x002d # All Users\Templates 90 | CSIDL_COMMON_VIDEO = 0x0037 # All Users\My Video 91 | CSIDL_COMPUTERSNEARME = 0x003d # Computers Near Me (computered from Workgroup membership) 92 | CSIDL_CONNECTIONS = 0x0031 # Network and Dial-up Connections 93 | CSIDL_CONTROLS = 0x0003 # My Computer\Control Panel 94 | CSIDL_COOKIES = 0x0021 95 | CSIDL_DESKTOP = 0x0000 # 96 | CSIDL_DESKTOPDIRECTORY = 0x0010 # \Desktop 97 | CSIDL_DRIVES = 0x0011 # My Computer 98 | CSIDL_FAVORITES = 0x0006 # \Favorites 99 | CSIDL_FONTS = 0x0014 # windows\fonts 100 | CSIDL_HISTORY = 0x0022 101 | CSIDL_INTERNET = 0x0001 # Internet Explorer (icon on desktop) 102 | CSIDL_INTERNET_CACHE = 0x0020 103 | CSIDL_LOCAL_APPDATA = 0x001c # \Local Settings\Applicaiton Data (non roaming) 104 | CSIDL_MYDOCUMENTS = 0x0005 # My Documents 105 | CSIDL_MYMUSIC = 0x000d # "My Music" folder 106 | CSIDL_MYPICTURES = 0x0027 # C:\Program Files\My Pictures 107 | CSIDL_MYVIDEO = 0x000e # "My Videos" folder 108 | CSIDL_NETHOOD = 0x0013 # \nethood 109 | CSIDL_NETWORK = 0x0012 # Network Neighborhood (My Network Places) 110 | CSIDL_PERSONAL = 0x0005 # Personal was just a silly name for My Documents 111 | CSIDL_PRINTERS = 0x0004 # My Computer\Printers 112 | CSIDL_PRINTHOOD = 0x001b # \PrintHood 113 | CSIDL_PROFILE = 0x0028 # USERPROFILE 114 | CSIDL_PROGRAM_FILES = 0x0026 # C:\Program Files 115 | CSIDL_PROGRAM_FILES_COMMON = 0x002b # C:\Program Files\Common 116 | CSIDL_PROGRAM_FILES_COMMONX86 = 0x002c # x86 Program Files\Common on RISC 117 | CSIDL_PROGRAM_FILESX86 = 0x002a # x86 C:\Program Files on RISC 118 | CSIDL_PROGRAMS = 0x0002 # Start Menu\Programs 119 | CSIDL_RECENT = 0x0008 # \Recent 120 | CSIDL_RESOURCES = 0x0038 # Resource Direcotry 121 | CSIDL_RESOURCES_LOCALIZED = 0x0039 # Localized Resource Direcotry 122 | CSIDL_SENDTO = 0x0009 # \SendTo 123 | CSIDL_STARTMENU = 0x000b # \Start Menu 124 | CSIDL_STARTUP = 0x0007 # Start Menu\Programs\Startup 125 | CSIDL_SYSTEM = 0x0025 # GetSystemDirectory() 126 | CSIDL_SYSTEMX86 = 0x0029 # x86 system directory on RISC 127 | CSIDL_TEMPLATES = 0x0015 128 | CSIDL_WINDOWS = 0x0024 # GetWindowsDirectory() 129 | } 130 | 131 | # KNOWNFOLDERID CSIDL equivalents: 132 | # https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx 133 | $KnownFolderIdToCsidl = @{ 134 | AdminTools = 'CSIDL_ADMINTOOLS' 135 | CDBurning = 'CSIDL_CDBURN_AREA' 136 | CommonAdminTools = 'CSIDL_COMMON_ADMINTOOLS' 137 | CommonOEMLinks = 'CSIDL_COMMON_OEM_LINKS' 138 | CommonPrograms = 'CSIDL_COMMON_PROGRAMS' 139 | CommonStartMenu = 'CSIDL_COMMON_STARTMENU' 140 | CommonStartup = 'CSIDL_COMMON_STARTUP', 'CSIDL_COMMON_ALTSTARTUP' 141 | CommonTemplates = 'CSIDL_COMMON_TEMPLATES' 142 | ComputerFolder = 'CSIDL_DRIVES' 143 | ConnectionsFolder = 'CSIDL_CONNECTIONS' 144 | ControlPanelFolder = 'CSIDL_CONTROLS' 145 | Cookies = 'CSIDL_COOKIES' 146 | Desktop = 'CSIDL_DESKTOP', 'CSIDL_DESKTOPDIRECTORY' 147 | Documents = 'CSIDL_MYDOCUMENTS', 'CSIDL_PERSONAL' 148 | Favorites = 'CSIDL_FAVORITES', 'CSIDL_COMMON_FAVORITES' 149 | Fonts = 'CSIDL_FONTS' 150 | History = 'CSIDL_HISTORY' 151 | InternetCache = 'CSIDL_INTERNET_CACHE' 152 | InternetFolder = 'CSIDL_INTERNET' 153 | LocalAppData = 'CSIDL_LOCAL_APPDATA' 154 | LocalizedResourcesDir = 'CSIDL_RESOURCES_LOCALIZED' 155 | Music = 'CSIDL_MYMUSIC' 156 | NetHood = 'CSIDL_NETHOOD' 157 | NetworkFolder = 'CSIDL_NETWORK', 'CSIDL_COMPUTERSNEARME' 158 | Pictures = 'CSIDL_MYPICTURES' 159 | PrintersFolder = 'CSIDL_PRINTERS' 160 | PrintHood = 'CSIDL_PRINTHOOD' 161 | Profile = 'CSIDL_PROFILE' 162 | ProgramData = 'CSIDL_COMMON_APPDATA' 163 | ProgramFiles = 'CSIDL_PROGRAM_FILES' 164 | ProgramFilesCommon = 'CSIDL_PROGRAM_FILES_COMMON' 165 | ProgramFilesCommonX86 = 'CSIDL_PROGRAM_FILES_COMMONX86' 166 | ProgramFilesX86 = 'CSIDL_PROGRAM_FILESX86' 167 | Programs = 'CSIDL_PROGRAMS' 168 | PublicDesktop = 'CSIDL_COMMON_DESKTOPDIRECTORY' 169 | PublicDocuments = 'CSIDL_COMMON_DOCUMENTS' 170 | PublicMusic = 'CSIDL_COMMON_MUSIC' 171 | PublicPictures = 'CSIDL_COMMON_PICTURES' 172 | PublicVideos = 'CSIDL_COMMON_VIDEO' 173 | Recent = 'CSIDL_RECENT' 174 | RecycleBinFolder = 'CSIDL_BITBUCKET' 175 | ResourceDir = 'CSIDL_RESOURCES' 176 | RoamingAppData = 'CSIDL_APPDATA' 177 | SendTo = 'CSIDL_SENDTO' 178 | StartMenu = 'CSIDL_STARTMENU' 179 | Startup = 'CSIDL_STARTUP', 'CSIDL_ALTSTARTUP' 180 | System = 'CSIDL_SYSTEM' 181 | SystemX86 = 'CSIDL_SYSTEMX86' 182 | Templates = 'CSIDL_TEMPLATES' 183 | Videos = 'CSIDL_MYVIDEO' 184 | Windows = 'CSIDL_WINDOWS' 185 | } 186 | 187 | $DynamicParameters = @{ 188 | Name = 'Name' 189 | Type = [string[]] 190 | Parameter = @{ 191 | Position = 0 192 | ValueFromPipeline = $true 193 | ValueFromPipelineByPropertyName = $true 194 | ParameterSetName = 'Name' 195 | } 196 | ValidateSet = $CsidlEnum.Keys + $KnownFolderIdToCsidl.Keys 197 | }, 198 | @{ 199 | Name = 'Csidl' 200 | Type = [string[]] 201 | Parameter = @{ 202 | Position = 0 203 | ValueFromPipelineByPropertyName = $true 204 | ParameterSetName = 'Csidl' 205 | } 206 | ValidateSet = $CsidlEnum.Keys 207 | }, 208 | @{ 209 | Name = 'KnownFolderId' 210 | Type = [Environment+SpecialFolder[]] 211 | Parameter = @{ 212 | Position = 0 213 | ValueFromPipelineByPropertyName = $true 214 | ParameterSetName = 'KnownFolderId' 215 | } 216 | } 217 | 218 | # Create the dictionary 219 | $RuntimeParameterDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary 220 | 221 | foreach($dp in $DynamicParameters) 222 | { 223 | # Create the collection of attributes 224 | $AttributeCollection = New-Object -TypeName System.Collections.ObjectModel.Collection[System.Attribute] 225 | 226 | # Create and set the parameters' attributes (Parameter, ValidateSet) 227 | 228 | # [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)] 229 | $AttributeCollection.Add( 230 | (New-Object -TypeName System.Management.Automation.ParameterAttribute -Property $dp.Parameter) 231 | ) 232 | 233 | # [ValidateSet()] 234 | if($dp.ValidateSet) 235 | { 236 | $AttributeCollection.Add( 237 | (New-Object -TypeName System.Management.Automation.ValidateSetAttribute -ArgumentList $dp.ValidateSet) 238 | ) 239 | } 240 | 241 | # Create and return the dynamic parameter 242 | $RuntimeParameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter( 243 | $dp.Name, 244 | $dp.Type, 245 | $AttributeCollection 246 | ) 247 | $RuntimeParameterDictionary.Add($dp.Name, $RuntimeParameter) 248 | } 249 | 250 | # Return dicitonary 251 | $RuntimeParameterDictionary 252 | } 253 | 254 | Begin 255 | { 256 | Write-Verbose 'Checking if [System.Environment]::GetFolderPath available' 257 | $GetFolderPath = $false 258 | try 259 | { 260 | $GetFolderPath = [bool][System.Environment].GetMethod('GetFolderPath') 261 | } 262 | catch [System.Reflection.AmbiguousMatchException] 263 | { 264 | $GetFolderPath = $true 265 | } 266 | 267 | Write-Verbose "Result: $GetFolderPath" 268 | 269 | $hwnd = New-Object -TypeName UIntPtr 270 | $lpData = New-Object -TypeName System.Text.StringBuilder(260) # MAX_PATH = 256 271 | 272 | function Register-Shell32Tools 273 | { 274 | if(![bool]('Shell32.Tools' -as [Type])) 275 | { 276 | $Enum = $(foreach($key in $CsidlEnum.GetEnumerator()){'{0}{1} = {2}' -f [System.Environment]::NewLine, $key.Name, $key.Value}) -join ',' 277 | $Shell32Tools = @' 278 | [DllImport("Shell32.dll")] 279 | public static extern int SHGetSpecialFolderPath( 280 | UIntPtr hwndOwner, 281 | System.Text.StringBuilder lpszPath, 282 | CSIDL iCsidl, 283 | int fCreate 284 | ); 285 | 286 | public enum CSIDL : int {{{0} 287 | }} 288 | '@ 289 | Write-Verbose 'Registering type: Shell32.Tools' 290 | Add-Type -MemberDefinition ($Shell32Tools -f $Enum) -Name Tools -Namespace Shell32 -Using System.Text -ErrorAction Stop 291 | } 292 | else 293 | { 294 | Write-Verbose 'Type already registered: Shell32.Tools' 295 | } 296 | } 297 | 298 | function Get-FolderPathByCsidl 299 | { 300 | Param 301 | ( 302 | [Parameter(Mandatory=$true)] 303 | [UIntPtr]$hwnd, 304 | 305 | [Parameter(Mandatory=$true)] 306 | [System.Text.StringBuilder]$lpData, 307 | 308 | [Parameter(Mandatory=$true)] 309 | [string[]]$Csidl 310 | ) 311 | 312 | Register-Shell32Tools 313 | 314 | Write-Verbose "Processing CSIDL(s): $($Csidl -join ', ')" 315 | 316 | foreach($item in $Csidl) 317 | { 318 | if([Shell32.Tools]::SHGetSpecialFolderPath($hwnd, $lpData, $item, 0)) 319 | { 320 | $lpData.ToString() 321 | } 322 | else 323 | { 324 | Write-Error "Folder not found: $item" 325 | } 326 | } 327 | } 328 | 329 | function Get-FolderPathByKnownFolderId 330 | { 331 | Param 332 | ( 333 | [Parameter(Mandatory = $true)] 334 | [System.Environment+SpecialFolder[]]$Path 335 | ) 336 | 337 | Write-Verbose "Processing KnownFolderId(s): $($Path -join ', ')" 338 | foreach($item in $Path) 339 | { 340 | [Environment]::GetFolderPath($item) 341 | } 342 | } 343 | } 344 | 345 | Process 346 | { 347 | if($PSBoundParameters.Name) 348 | { 349 | foreach($item in $PSBoundParameters.Name) 350 | { 351 | if($item -like 'CSIDL_*') 352 | { 353 | Get-FolderPathByCsidl -hwnd $hwnd -lpData $lpData -Csidl $item 354 | } 355 | else 356 | { 357 | if($GetFolderPath -and [System.Enum]::GetNames('System.Environment+SpecialFolder') -contains $item) 358 | { 359 | Get-FolderPathByKnownFolderId -Path $item 360 | } 361 | else 362 | { 363 | Write-Verbose "No KnownFolderId for: '$item', trying to map to CSIDL" 364 | $MappedCsidl = $KnownFolderIdToCsidl[$item] 365 | 366 | if($MappedCsidl) 367 | { 368 | Write-Verbose "KnownFolderId '$item' is mapped to CSIDL(s): $($MappedCsidl -join ', ')" 369 | Get-FolderPathByCsidl -hwnd $hwnd -lpData $lpData -Csidl $MappedCsidl | Select-Object -Unique 370 | } 371 | else 372 | { 373 | Write-Error "Folder not found. There is no known CSIDL mapping for '$item'" 374 | } 375 | } 376 | 377 | } 378 | } 379 | } 380 | elseif($PSBoundParameters.Csidl) 381 | { 382 | Get-FolderPathByCsidl -hwnd $hwnd -lpData $lpData -Csidl $PSBoundParameters.Csidl 383 | } 384 | elseif($PSBoundParameters.KnownFolderId) 385 | { 386 | Get-FolderPathByKnownFolderId -Path $PSBoundParameters.KnownFolderId 387 | } 388 | else 389 | { 390 | throw 'No valid parameters supplied!' 391 | } 392 | } 393 | } -------------------------------------------------------------------------------- /Get-SvnAuthor.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Get list of unique commit authors in SVN repository. 4 | 5 | .Description 6 | Get list of unique commit authors in one or more SVN repositories. Requires Subversion binaries. 7 | 8 | .Parameter Url 9 | This parameter is required. 10 | 11 | An array of strings representing URLs to the SVN repositories. 12 | 13 | .Parameter User 14 | This parameter is optional. 15 | 16 | A string specifying username for SVN repository. 17 | 18 | .Parameter Password 19 | This parameter is optional. 20 | 21 | A string specifying password for SVN repository. 22 | 23 | .Parameter SvnPath 24 | This parameter is optional. 25 | 26 | A string specifying path to the svn.exe. Use it if Subversion binaries is not in your path variable, or you wish to use specific version. 27 | 28 | .Example 29 | Get-SvnAuthor -Url 'http://svnserver/svn/project' 30 | 31 | Description 32 | ----------- 33 | Get list of unique commit authors for SVN repository http://svnserver/svn/project 34 | 35 | .Example 36 | Get-SvnAuthor -Url 'http://svnserver/svn/project' -User john -Password doe 37 | 38 | Description 39 | ----------- 40 | Get list of unique commit authors for SVN repository http://svnserver/svn/project using username and password. 41 | 42 | .Example 43 | Get-SvnAuthor -Url 'http://svnserver/svn/project' -SvnPath 'C:\Program Files (x86)\VisualSVN Server\bin\svn.exe' 44 | 45 | Description 46 | ----------- 47 | Get list of unique commit authors for SVN repository http://svnserver/svn/project using custom svn.exe binary. 48 | 49 | .Example 50 | Get-SvnAuthor -Url 'http://svnserver/svn/project_1', 'http://svnserver/svn/project_2' 51 | 52 | Description 53 | ----------- 54 | Get list of unique commit authors for two SVN repositories: http://svnserver/svn/project_1 and http://svnserver/svn/project_2. 55 | 56 | .Example 57 | 'http://svnserver/svn/project_1', 'http://svnserver/svn/project_2' | Get-SvnAuthor 58 | 59 | Description 60 | ----------- 61 | Get list of unique commit authors for two SVN repositories: http://svnserver/svn/project_1 and http://svnserver/svn/project_2. 62 | #> 63 | function Get-SvnAuthor 64 | { 65 | [CmdletBinding()] 66 | Param 67 | ( 68 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 69 | [ValidateNotNullOrEmpty()] 70 | [string[]]$Url, 71 | 72 | [Parameter(ValueFromPipelineByPropertyName = $true)] 73 | [ValidateNotNullOrEmpty()] 74 | [string]$User, 75 | 76 | [Parameter(ValueFromPipelineByPropertyName = $true)] 77 | [ValidateNotNullOrEmpty()] 78 | [string]$Password, 79 | 80 | [ValidateScript({ 81 | if(Test-Path -LiteralPath $_ -PathType Leaf) 82 | { 83 | $true 84 | } 85 | else 86 | { 87 | throw "$_ not found!" 88 | } 89 | })] 90 | [ValidateNotNullOrEmpty()] 91 | [string]$SvnPath = 'svn.exe' 92 | ) 93 | 94 | Begin 95 | { 96 | if(!(Get-Command -Name $SvnPath -CommandType Application -ErrorAction SilentlyContinue)) 97 | { 98 | throw "$SvnPath not found!" 99 | } 100 | $ret = @() 101 | } 102 | 103 | Process 104 | { 105 | $Url | ForEach-Object { 106 | $SvnCmd = @('log', $_, '--xml', '--quiet', '--non-interactive') + $(if($User){@('--username', $User)}) + $(if($Password){@('--password', $Password)}) 107 | $SvnLog = &$SvnPath $SvnCmd *>&1 108 | 109 | if($LastExitCode) 110 | { 111 | Write-Error ($SvnLog | Out-String) 112 | } 113 | else 114 | { 115 | $ret += [xml]$SvnLog | ForEach-Object {$_.log.logentry.author} 116 | } 117 | } 118 | } 119 | 120 | End 121 | { 122 | $ret | Sort-Object -Unique 123 | } 124 | } -------------------------------------------------------------------------------- /Get-TerminologyTranslation.ps1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beatcracker/Powershell-Misc/d0704cea3d1eb948f9720dc8826a630e86150c08/Get-TerminologyTranslation.ps1 -------------------------------------------------------------------------------- /Import-Component.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Bulk-import from folder any component, supported by PowerShell (script, module, source code, .Net assembly). 4 | 5 | .Description 6 | Bulk-import from folder any component, supported by PowerShell. Supported components: 7 | 8 | * Script (.ps1) - imported using Dot-Sourcing. 9 | * Module (.psm1) - imported using Import-Module cmdlet 10 | * Source code (.cs, .vb, .js) - imported using Add-Type cmdlet 11 | * .Net assembly (.dll) - imported using Add-Type cmdlet 12 | 13 | WARNING: To import .PS1 scripts this function itself has to be dot-sourced! See examples. 14 | 15 | .Parameter Path 16 | This parameter is required. 17 | 18 | A string representing the path, where components located. Wildcards are not supported. 19 | This is the folder, where you could keep your functions in .ps1 scripts, modules, 20 | C# (VB.Net, JavaScript) code in .cs, .vb, .js files and .Net assemblies (.dll). 21 | 22 | .Parameter Recurse 23 | This parameter is optional. 24 | 25 | A switch indicating whether or not to recurse into subdirectories. Default is no recursion. 26 | 27 | .Parameter Type 28 | This parameter is optional. 29 | 30 | An array of component types, that would be imported. If this parameter is not specified, 31 | any supported component will be imported. 32 | 33 | WARNING: To import .PS1 scripts this function itself has to be dot-sourced! See examples. 34 | 35 | The value can be any combination of the following: 36 | 37 | Ps 38 | Script (.ps1) - imported using Dot-Sourcing. 39 | 40 | Psm 41 | Module - imported using Import-Module cmdlet 42 | 43 | This function will only try to import well-formed modules. A "well-formed" module is a module 44 | that is stored in a directory that has the same name as the base name of at least one file in 45 | the module directory. If a module is not well-formed, Windows PowerShell does not recognize it 46 | as a module. 47 | 48 | The "base name" of a file is the name without the file name extension. In a well-formed module, 49 | the name of the directory that contains the module files must match the base name of at least 50 | one file in the module. 51 | 52 | More info: https://msdn.microsoft.com/en-us/library/dd878350.aspx 53 | 54 | Cs 55 | Source code (.cs) - imported using Add-Type cmdlet 56 | 57 | Vb 58 | Source code (.vb) - imported using Add-Type cmdlet 59 | 60 | Js 61 | Source code (.js) - imported using Add-Type cmdlet 62 | 63 | Asm 64 | .Net assembly (.dll) - imported using Add-Type cmdlet 65 | 66 | .Parameter Include 67 | This parameter is optional. 68 | 69 | An array of strings. Only file names without extension that match will be imported. Wildcards are permitted. 70 | 71 | .Parameter Exclude 72 | This parameter is optional. 73 | 74 | An array of strings. File names without extension that match will not be imported. Has priority over the Include parameter. 75 | Wildcards are permitted. 76 | 77 | .Example 78 | . Import-Component 'C:\PsLib' 79 | 80 | Description 81 | ----------- 82 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll) from folder 'C:\PsLib'. 83 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 84 | 85 | .Example 86 | 'C:\PsLib', 'C:\MyLib' | . Import-Component 87 | 88 | Description 89 | ----------- 90 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll) from folders 'C:\PsLib' and 'C:\MyLib'. 91 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 92 | 93 | .Example 94 | . Import-Component 'C:\PsLib' -Recurse 95 | 96 | Description 97 | ----------- 98 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll), recurse into subdirectories. 99 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 100 | 101 | .Example 102 | . Import-Component 'C:\PsLib' -Recurse -Include 'MyScript*' 103 | 104 | Description 105 | ----------- 106 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll), recurse into subdirectories. 107 | Include only files with names without extension that match wildcard 'MyScript*'. 108 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 109 | 110 | .Example 111 | . Import-Component 'C:\PsLib' -Recurse -Include 'MyScript*' -Exclude '*_backup*' 112 | 113 | Description 114 | ----------- 115 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll), recurse into subdirectories. 116 | Include only files with names without extension that match wildcard 'MyScript*'. 117 | Exclude files with names without extension that match '*_backup*' wildcard. 118 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 119 | 120 | .Example 121 | . Import-Component 'C:\PsLib' -Recurse -Include 'MyScript*','*MyLib*' -Exclude '*_backup*','*_old*' 122 | 123 | Description 124 | ----------- 125 | Import all supported components (.ps1, modules, .cs, .vb, .js, .dll), recurse into subdirectories. 126 | Include only files with names without extension that match wildcards 'MyScript*' and '*MyLib*'. 127 | Exclude files with names without extension that match '*_backup*' and '*_old*' wildcards. 128 | Note, that to be able to import .ps1 scripts into the current scope, this function is dot-sourced. 129 | 130 | .Example 131 | Import-Component 'C:\PsLib' -Type Psm 132 | 133 | Description 134 | ----------- 135 | Import Powershell modules only. 136 | 137 | .Example 138 | Import-Component 'C:\PsLib' -Type Ps,Cs,Asm 139 | 140 | Description 141 | ----------- 142 | Import Powershell modules, C# source code (.cs), .Net assemblies (.dll). 143 | #> 144 | function Import-Component 145 | { 146 | [CmdletBinding()] 147 | Param() 148 | DynamicParam 149 | { 150 | # Stripped down version of the New-DynamicParameter function 151 | # https://gallery.technet.microsoft.com/scriptcenter/New-DynamicParameter-63389a46 152 | New-Module -OutVariable null -ReturnResult -AsCustomObject -ScriptBlock { 153 | $DynamicParameters = @( 154 | @{ 155 | Name = 'Path' 156 | Type = [string] 157 | Position = 0 158 | Mandatory = $true 159 | ValueFromPipeline = $true 160 | ValueFromPipelineByPropertyName = $true 161 | ValidateScript = {Test-Path -LiteralPath $_ -PathType Container} 162 | }, 163 | @{ 164 | Name = 'Type' 165 | Type = [string[]] 166 | Position = 1 167 | ValueFromPipelineByPropertyName = $true 168 | ValidateSet = 'Ps','Psm','Cs','Vb','Js', 'Asm' 169 | }, 170 | @{ 171 | Name = 'Include' 172 | Type = [string] 173 | Position = 2 174 | ValueFromPipelineByPropertyName = $true 175 | } 176 | @{ 177 | Name = 'Exclude' 178 | Type = [string] 179 | Position = 3 180 | ValueFromPipelineByPropertyName = $true 181 | }, 182 | @{ 183 | Name = 'Recurse' 184 | Type = [switch] 185 | Position = 4 186 | ValueFromPipelineByPropertyName = $true 187 | } 188 | ) 189 | # Creating new dynamic parameters dictionary 190 | $Dictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary 191 | 192 | # Strings to match attributes and validation arguments 193 | $AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$' 194 | $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$' 195 | $AliasRegex = '^Alias$' 196 | 197 | $DynamicParameters | ForEach-Object { 198 | # Creating new parameter''s attirubutes object' 199 | $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute 200 | 201 | # Looping through the bound parameters, setting attirubutes 202 | $CurrentParam = $_ 203 | switch -regex ($_.Keys) 204 | { 205 | $AttributeRegex 206 | { 207 | Try 208 | { 209 | # Adding new parameter attribute 210 | $ParameterAttribute.$_ = $CurrentParam[$_] 211 | } 212 | Catch {$_} 213 | continue 214 | } 215 | } 216 | 217 | # Looping through the bound parameters, setting attirubutes 218 | switch -regex ($_.Keys) 219 | { 220 | $AttributeRegex 221 | { 222 | Try 223 | { 224 | # Adding new parameter attribute 225 | $ParameterAttribute.$_ = $CurrentParam[$_] 226 | } 227 | Catch {$_} 228 | continue 229 | } 230 | } 231 | 232 | # Creating new attribute collection object 233 | $AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute] 234 | 235 | # Looping through the bound parameters, adding attributes 236 | switch -regex ($_.Keys) 237 | { 238 | $ValidationRegex 239 | { 240 | Try 241 | { 242 | # Adding attribute 243 | $ParameterOptions = New-Object -TypeName "System.Management.Automation.$_`Attribute" -ArgumentList $CurrentParam[$_] -ErrorAction SilentlyContinue 244 | $AttributeCollection.Add($ParameterOptions) 245 | } 246 | Catch {$_} 247 | continue 248 | } 249 | 250 | $AliasRegex 251 | { 252 | Try 253 | { 254 | # Adding alias 255 | $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList $CurrentParam[$_] -ErrorAction SilentlyContinue 256 | $AttributeCollection.Add($CurrentParam[$_]) 257 | continue 258 | } 259 | Catch {$_} 260 | } 261 | } 262 | 263 | # Adding attributes to the attribute collection 264 | $AttributeCollection.Add($ParameterAttribute) 265 | 266 | # Finishing creation of the new dynamic parameter 267 | $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($_.Name, $_.Type, $AttributeCollection) 268 | 269 | # Adding dynamic parameter to the dynamic parameter dictionary 270 | $Dictionary.Add($_.Name, $Parameter) 271 | } 272 | 273 | # Writing dynamic parameter dictionary to the pipeline 274 | $Dictionary 275 | } 276 | } 277 | 278 | Process 279 | { 280 | New-Module -OutVariable null -ReturnResult -AsCustomObject -ScriptBlock { 281 | Param 282 | ( 283 | $Path, 284 | $Type, 285 | $Exclude, 286 | $Include, 287 | $Recurse, 288 | $DotSource, 289 | $AddType, 290 | $ImportModule, 291 | $IsDotSourced, 292 | $SessionState 293 | ) 294 | 295 | # Inherit parent's function preferences 296 | # More info: https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d 297 | $PreferenceVars = @( 298 | 'ErrorView', 'FormatEnumerationLimit', 'LogCommandHealthEvent', 'LogCommandLifecycleEvent', 299 | 'LogEngineHealthEvent', 'LogEngineLifecycleEvent', 'LogProviderHealthEvent', 300 | 'LogProviderLifecycleEvent', 'MaximumAliasCount', 'MaximumDriveCount', 'MaximumErrorCount', 301 | 'MaximumFunctionCount', 'MaximumHistoryCount', 'MaximumVariableCount', 'OFS', 'OutputEncoding', 302 | 'ProgressPreference', 'PSDefaultParameterValues', 'PSEmailServer', 'PSModuleAutoLoadingPreference', 303 | 'PSSessionApplicationName', 'PSSessionConfigurationName', 'PSSessionOption', 'ErrorActionPreference', 304 | 'DebugPreference', 'ConfirmPreference', 'WhatIfPreference', 'VerbosePreference', 'WarningPreference' 305 | ) 306 | $PreferenceVars | ForEach-Object { 307 | Set-Variable -Name $_ -Value $SessionState.PSVariable.GetValue($_) -Force 308 | } 309 | 310 | # Set default value for Include 311 | if(!$Include){ 312 | $Include = '*' 313 | } 314 | 315 | # Define extensions for file types and assign import commands 316 | $FileType = @{ 317 | Ps = @{Extension = '.ps1' ; Command = $DotSource} 318 | Psm = @{Extension = [string]::Empty ; Command = $ImportModule} 319 | Cs = @{Extension = '.cs' ; Command = $AddType} 320 | Vb = @{Extension = '.vb' ; Command = $AddType} 321 | Js = @{Extension = '.js' ; Command = $AddType} 322 | Asm = @{Extension = '.dll' ; Command = $AddType} 323 | } 324 | 325 | if($Type) 326 | { 327 | Write-Verbose "Trying to import file types $($Type -join ',')" 328 | [array]$FileTypeToImport = $Type | Sort-Object -Unique | ForEach-Object {$FileType.$_} 329 | } 330 | else 331 | { 332 | Write-Verbose 'Trying to import all supported file types' 333 | [array]$FileTypeToImport = $FileType.GetEnumerator() | ForEach-Object {$_.Value} 334 | } 335 | 336 | if(($FileTypeToImport).Extension -contains '.ps1' -and !$IsDotSourced.True) 337 | { 338 | Write-Warning "To import .PS1 scripts this function itself has to be dot-sourced! Example: $($IsDotSourced.Example)" 339 | } 340 | 341 | $FileTypeToImport | 342 | ForEach-Object { 343 | # We need to pass current file type to the filter function later 344 | $Private:currFT = $_ 345 | Write-Verbose "Searching path '$($Path)' for file type '$(('*' + $_.Extension))'" 346 | Get-ChildItem -LiteralPath $Path -Filter ('*' + $_.Extension) -Recurse:$Recurse | 347 | # Process Include\Exclude parameters 348 | Where-Object { 349 | # No file extension and directory = module, else - file 350 | ((!$Private:currFT.Extension -and $_.PSIsContainer) -or ($Private:currFT.Extension -and !$_.PSIsContainer)) -and 351 | # Include\Exclude filters with wildcard support 352 | $($_ | 353 | Where-Object {$tmp = $_.Basename ; ($Include | Where-Object {$tmp -like $_})} | 354 | Where-Object {$tmp = $_.Basename ; !($Exclude | Where-Object {$tmp -like $_})}) 355 | } | 356 | ForEach-Object { 357 | Try 358 | { 359 | Write-Verbose "Trying to import: $_" 360 | . $Private:currFT.Command $_ 361 | $Success = $true 362 | $ErrorMessage = $null 363 | Write-Verbose 'Success' 364 | } 365 | Catch 366 | { 367 | Write-Verbose 'Failure' 368 | $Success = $false 369 | $ErrorMessage = $_ 370 | } 371 | 372 | $ret = @{ 373 | Name = $_.Name 374 | Path = $_.FullName 375 | ErrorMessage = $ErrorMessage 376 | Success = $Success 377 | } 378 | 379 | Write-Verbose "Writing import status for '$_' to pipeline" 380 | New-Object -TypeName PSObject -Property $ret | Select-Object -Property Name, Path, ErrorMessage, Success 381 | } 382 | } 383 | } -ArgumentList ( 384 | $PSBoundParameters.Path, 385 | $PSBoundParameters.Type, 386 | $PSBoundParameters.Exclude, 387 | $PSBoundParameters.Include, 388 | [bool]$PSBoundParameters.Recurse, 389 | # Execution in this scope is required to import PS1 files 390 | # To be executed in this scope (even when they passed to the module) scriptblocks have to be defined here 391 | # http://stackoverflow.com/questions/2193410/strange-behavior-with-powershell-scriptblock-variable-scope-and-modules-any-sug/27495377#27495377 392 | {. $args[0].FullName}, 393 | {Add-Type -LiteralPath $args[0].FullName -ErrorAction SilentlyContinue}, 394 | { 395 | if(Get-ChildItem -Path ($args[0].FullName + '\*') -Filter ($args[0].Basename + '.*')) 396 | { 397 | Write-Verbose "Folder '$($args[0].Basename)' looks like well-formed module" 398 | # https://msdn.microsoft.com/en-us/library/dd878350.aspx 399 | Import-Module -Name $args[0].FullName -ErrorAction SilentlyContinue 400 | } 401 | else 402 | {throw "Folder '$($args[0].Basename)' is not a well-formed module"} 403 | }, 404 | ( 405 | New-Module -OutVariable null -ReturnResult -AsCustomObject -ScriptBlock { 406 | # Is function dot-sourced? 407 | $MyCommand = (Get-PSCallStack)[2].Position.Text 408 | $MyCommandName = (Get-PSCallStack)[1].Command 409 | 410 | @{ 411 | True = $MyCommand -match "\.\s+$MyCommandName\s+" 412 | Example = $MyCommand -replace "(^.*)($MyCommandName)(.*$)", '$1. $2$3' 413 | } 414 | } 415 | ), 416 | $PSCmdlet.SessionState 417 | ) 418 | } 419 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (Ms-PL) 2 | 3 | This license governs use of the accompanying software. If you use the 4 | software, you accept this license. If you do not accept the license, do not 5 | use the software. 6 | 7 | 1. Definitions 8 | 9 | The terms "reproduce," "reproduction," "derivative works," and 10 | "distribution" have the same meaning here as under U.S. copyright law. 11 | 12 | A "contribution" is the original software, or any additions or changes to 13 | the software. 14 | 15 | A "contributor" is any person that distributes its contribution under this 16 | license. 17 | 18 | "Licensed patents" are a contributor's patent claims that read directly on 19 | its contribution. 20 | 21 | 2. Grant of Rights 22 | 23 | (A) Copyright Grant- Subject to the terms of this license, including the 24 | license conditions and limitations in section 3, each contributor grants 25 | you a non-exclusive, worldwide, royalty-free copyright license to reproduce 26 | its contribution, prepare derivative works of its contribution, and 27 | distribute its contribution or any derivative works that you create. 28 | 29 | (B) Patent Grant- Subject to the terms of this license, including the 30 | license conditions and limitations in section 3, each contributor grants 31 | you a non-exclusive, worldwide, royalty-free license under its licensed 32 | patents to make, have made, use, sell, offer for sale, import, and/or 33 | otherwise dispose of its contribution in the software or derivative works 34 | of the contribution in the software. 35 | 36 | 3. Conditions and Limitations 37 | 38 | (A) No Trademark License- This license does not grant you rights to use any 39 | contributors' name, logo, or trademarks. 40 | 41 | (B) If you bring a patent claim against any contributor over patents that 42 | you claim are infringed by the software, your patent license from such 43 | contributor to the software ends automatically. 44 | 45 | (C) If you distribute any portion of the software, you must retain all 46 | copyright, patent, trademark, and attribution notices that are present in 47 | the software. 48 | 49 | (D) If you distribute any portion of the software in source code form, you 50 | may do so only under this license by including a complete copy of this 51 | license with your distribution. If you distribute any portion of the 52 | software in compiled or object code form, you may only do so under a 53 | license that complies with this license. 54 | 55 | (E) The software is licensed "as-is." You bear the risk of using it. The 56 | contributors give no express warranties, guarantees or conditions. You may 57 | have additional consumer rights under your local laws which this license 58 | cannot change. To the extent permitted under your local laws, the 59 | contributors exclude the implied warranties of merchantability, fitness for 60 | a particular purpose and non-infringement. 61 | -------------------------------------------------------------------------------- /New-DynamicParameter.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Helper function to simplify creating dynamic parameters 4 | 5 | .DESCRIPTION 6 | Helper function to simplify creating dynamic parameters. 7 | 8 | Example use cases: 9 | Include parameters only if your environment dictates it 10 | Include parameters depending on the value of a user-specified parameter 11 | Provide tab completion and intellisense for parameters, depending on the environment 12 | 13 | Please keep in mind that all dynamic parameters you create, will not have corresponding variables created. 14 | Use New-DynamicParameter with 'CreateVariables' switch in your main code block, 15 | ('Process' for advanced functions) to create those variables. 16 | Alternatively, manually reference $PSBoundParameters for the dynamic parameter value. 17 | 18 | This function has two operating modes: 19 | 20 | 1. All dynamic parameters created in one pass using pipeline input to the function. This mode allows to create dynamic parameters en masse, 21 | with one function call. There is no need to create and maintain custom RuntimeDefinedParameterDictionary. 22 | 23 | 2. Dynamic parameters are created by separate function calls and added to the RuntimeDefinedParameterDictionary you created beforehand. 24 | Then you output this RuntimeDefinedParameterDictionary to the pipeline. This allows more fine-grained control of the dynamic parameters, 25 | with custom conditions and so on. 26 | 27 | .NOTES 28 | Credits to jrich523 and ramblingcookiemonster for their initial code and inspiration: 29 | https://github.com/RamblingCookieMonster/PowerShell/blob/master/New-DynamicParam.ps1 30 | http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/ 31 | http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/ 32 | 33 | Credit to BM for alias and type parameters and their handling 34 | 35 | .PARAMETER Name 36 | Name of the dynamic parameter 37 | 38 | .PARAMETER Type 39 | Type for the dynamic parameter. Default is string 40 | 41 | .PARAMETER Alias 42 | If specified, one or more aliases to assign to the dynamic parameter 43 | 44 | .PARAMETER Mandatory 45 | If specified, set the Mandatory attribute for this dynamic parameter 46 | 47 | .PARAMETER Position 48 | If specified, set the Position attribute for this dynamic parameter 49 | 50 | .PARAMETER HelpMessage 51 | If specified, set the HelpMessage for this dynamic parameter 52 | 53 | .PARAMETER DontShow 54 | If specified, set the DontShow for this dynamic parameter. 55 | This is the new PowerShell 4.0 attribute that hides parameter from tab-completion. 56 | http://www.powershellmagazine.com/2013/07/29/pstip-hiding-parameters-from-tab-completion/ 57 | 58 | .PARAMETER ValueFromPipeline 59 | If specified, set the ValueFromPipeline attribute for this dynamic parameter 60 | 61 | .PARAMETER ValueFromPipelineByPropertyName 62 | If specified, set the ValueFromPipelineByPropertyName attribute for this dynamic parameter 63 | 64 | .PARAMETER ValueFromRemainingArguments 65 | If specified, set the ValueFromRemainingArguments attribute for this dynamic parameter 66 | 67 | .PARAMETER ParameterSetName 68 | If specified, set the ParameterSet attribute for this dynamic parameter. By default parameter is added to all parameters sets. 69 | 70 | .PARAMETER AllowNull 71 | If specified, set the AllowNull attribute of this dynamic parameter 72 | 73 | .PARAMETER AllowEmptyString 74 | If specified, set the AllowEmptyString attribute of this dynamic parameter 75 | 76 | .PARAMETER AllowEmptyCollection 77 | If specified, set the AllowEmptyCollection attribute of this dynamic parameter 78 | 79 | .PARAMETER ValidateNotNull 80 | If specified, set the ValidateNotNull attribute of this dynamic parameter 81 | 82 | .PARAMETER ValidateNotNullOrEmpty 83 | If specified, set the ValidateNotNullOrEmpty attribute of this dynamic parameter 84 | 85 | .PARAMETER ValidateRange 86 | If specified, set the ValidateRange attribute of this dynamic parameter 87 | 88 | .PARAMETER ValidateLength 89 | If specified, set the ValidateLength attribute of this dynamic parameter 90 | 91 | .PARAMETER ValidatePattern 92 | If specified, set the ValidatePattern attribute of this dynamic parameter 93 | 94 | .PARAMETER ValidateScript 95 | If specified, set the ValidateScript attribute of this dynamic parameter 96 | 97 | .PARAMETER ValidateSet 98 | If specified, set the ValidateSet attribute of this dynamic parameter 99 | 100 | .PARAMETER Dictionary 101 | If specified, add resulting RuntimeDefinedParameter to an existing RuntimeDefinedParameterDictionary. 102 | Appropriate for custom dynamic parameters creation. 103 | 104 | If not specified, create and return a RuntimeDefinedParameterDictionary 105 | Aappropriate for a simple dynamic parameter creation. 106 | 107 | .EXAMPLE 108 | Create one dynamic parameter. 109 | 110 | This example illustrates the use of New-DynamicParameter to create a single dynamic parameter. 111 | The Drive's parameter ValidateSet is populated with all available volumes on the computer for handy tab completion / intellisense. 112 | 113 | Usage: Get-FreeSpace -Drive 114 | 115 | function Get-FreeSpace 116 | { 117 | [CmdletBinding()] 118 | Param() 119 | DynamicParam 120 | { 121 | # Get drive names for ValidateSet attribute 122 | $DriveList = ([System.IO.DriveInfo]::GetDrives()).Name 123 | 124 | # Create new dynamic parameter 125 | New-DynamicParameter -Name Drive -ValidateSet $DriveList -Type ([array]) -Position 0 -Mandatory 126 | } 127 | 128 | Process 129 | { 130 | # Dynamic parameters don't have corresponding variables created, 131 | # you need to call New-DynamicParameter with CreateVariables switch to fix that. 132 | New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters 133 | 134 | $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object {$Drive -contains $_.Name} 135 | $DriveInfo | 136 | ForEach-Object { 137 | if(!$_.TotalFreeSpace) 138 | { 139 | $FreePct = 0 140 | } 141 | else 142 | { 143 | $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2) 144 | } 145 | New-Object -TypeName psobject -Property @{ 146 | Drive = $_.Name 147 | DriveType = $_.DriveType 148 | 'Free(%)' = $FreePct 149 | } 150 | } 151 | } 152 | } 153 | 154 | .EXAMPLE 155 | Create several dynamic parameters not using custom RuntimeDefinedParameterDictionary (requires piping). 156 | 157 | In this example two dynamic parameters are created. Each parameter belongs to the different parameter set, so they are mutually exclusive. 158 | 159 | The Drive's parameter ValidateSet is populated with all available volumes on the computer. 160 | The DriveType's parameter ValidateSet is populated with all available drive types. 161 | 162 | Usage: Get-FreeSpace -Drive 163 | or 164 | Usage: Get-FreeSpace -DriveType 165 | 166 | Parameters are defined in the array of hashtables, which is then piped through the New-Object to create PSObject and pass it to the New-DynamicParameter function. 167 | Because of piping, New-DynamicParameter function is able to create all parameters at once, thus eliminating need for you to create and pass external RuntimeDefinedParameterDictionary to it. 168 | 169 | function Get-FreeSpace 170 | { 171 | [CmdletBinding()] 172 | Param() 173 | DynamicParam 174 | { 175 | # Array of hashtables that hold values for dynamic parameters 176 | $DynamicParameters = @( 177 | @{ 178 | Name = 'Drive' 179 | Type = [array] 180 | Position = 0 181 | Mandatory = $true 182 | ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name 183 | ParameterSetName = 'Drive' 184 | }, 185 | @{ 186 | Name = 'DriveType' 187 | Type = [array] 188 | Position = 0 189 | Mandatory = $true 190 | ValidateSet = [System.Enum]::GetNames('System.IO.DriveType') 191 | ParameterSetName = 'DriveType' 192 | } 193 | ) 194 | 195 | # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter, 196 | # to create all dynamic paramters in one function call. 197 | $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter 198 | } 199 | Process 200 | { 201 | # Dynamic parameters don't have corresponding variables created, 202 | # you need to call New-DynamicParameter with CreateVariables switch to fix that. 203 | New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters 204 | 205 | if($Drive) 206 | { 207 | $Filter = {$Drive -contains $_.Name} 208 | } 209 | elseif($DriveType) 210 | { 211 | $Filter = {$DriveType -contains $_.DriveType} 212 | } 213 | 214 | $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter 215 | $DriveInfo | 216 | ForEach-Object { 217 | if(!$_.TotalFreeSpace) 218 | { 219 | $FreePct = 0 220 | } 221 | else 222 | { 223 | $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2) 224 | } 225 | New-Object -TypeName psobject -Property @{ 226 | Drive = $_.Name 227 | DriveType = $_.DriveType 228 | 'Free(%)' = $FreePct 229 | } 230 | } 231 | } 232 | } 233 | 234 | .EXAMPLE 235 | Create several dynamic parameters, with multiple Parameter Sets, not using custom RuntimeDefinedParameterDictionary (requires piping). 236 | 237 | In this example three dynamic parameters are created. Two of the parameters are belong to the different parameter set, so they are mutually exclusive. 238 | One of the parameters belongs to both parameter sets. 239 | 240 | The Drive's parameter ValidateSet is populated with all available volumes on the computer. 241 | The DriveType's parameter ValidateSet is populated with all available drive types. 242 | The DriveType's parameter ValidateSet is populated with all available drive types. 243 | The Precision's parameter controls number of digits after decimal separator for Free Space percentage. 244 | 245 | Usage: Get-FreeSpace -Drive -Precision 2 246 | or 247 | Usage: Get-FreeSpace -DriveType -Precision 2 248 | 249 | Parameters are defined in the array of hashtables, which is then piped through the New-Object to create PSObject and pass it to the New-DynamicParameter function. 250 | If parameter with the same name already exist in the RuntimeDefinedParameterDictionary, a new Parameter Set is added to it. 251 | Because of piping, New-DynamicParameter function is able to create all parameters at once, thus eliminating need for you to create and pass external RuntimeDefinedParameterDictionary to it. 252 | 253 | function Get-FreeSpace 254 | { 255 | [CmdletBinding()] 256 | Param() 257 | DynamicParam 258 | { 259 | # Array of hashtables that hold values for dynamic parameters 260 | $DynamicParameters = @( 261 | @{ 262 | Name = 'Drive' 263 | Type = [array] 264 | Position = 0 265 | Mandatory = $true 266 | ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name 267 | ParameterSetName = 'Drive' 268 | }, 269 | @{ 270 | Name = 'DriveType' 271 | Type = [array] 272 | Position = 0 273 | Mandatory = $true 274 | ValidateSet = [System.Enum]::GetNames('System.IO.DriveType') 275 | ParameterSetName = 'DriveType' 276 | }, 277 | @{ 278 | Name = 'Precision' 279 | Type = [int] 280 | # This will add a Drive parameter set to the parameter 281 | Position = 1 282 | ParameterSetName = 'Drive' 283 | }, 284 | @{ 285 | Name = 'Precision' 286 | # Because the parameter already exits in the RuntimeDefinedParameterDictionary, 287 | # this will add a DriveType parameter set to the parameter. 288 | Position = 1 289 | ParameterSetName = 'DriveType' 290 | } 291 | ) 292 | 293 | # Convert hashtables to PSObjects and pipe them to the New-DynamicParameter, 294 | # to create all dynamic paramters in one function call. 295 | $DynamicParameters | ForEach-Object {New-Object PSObject -Property $_} | New-DynamicParameter 296 | } 297 | Process 298 | { 299 | # Dynamic parameters don't have corresponding variables created, 300 | # you need to call New-DynamicParameter with CreateVariables switch to fix that. 301 | New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters 302 | 303 | if($Drive) 304 | { 305 | $Filter = {$Drive -contains $_.Name} 306 | } 307 | elseif($DriveType) 308 | { 309 | $Filter = {$DriveType -contains $_.DriveType} 310 | } 311 | 312 | if(!$Precision) 313 | { 314 | $Precision = 2 315 | } 316 | 317 | $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter 318 | $DriveInfo | 319 | ForEach-Object { 320 | if(!$_.TotalFreeSpace) 321 | { 322 | $FreePct = 0 323 | } 324 | else 325 | { 326 | $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), $Precision) 327 | } 328 | New-Object -TypeName psobject -Property @{ 329 | Drive = $_.Name 330 | DriveType = $_.DriveType 331 | 'Free(%)' = $FreePct 332 | } 333 | } 334 | } 335 | } 336 | 337 | .Example 338 | Create dynamic parameters using custom dictionary. 339 | 340 | In case you need more control, use custom dictionary to precisely choose what dynamic parameters to create and when. 341 | The example below will create DriveType dynamic parameter only if today is not a Friday: 342 | 343 | function Get-FreeSpace 344 | { 345 | [CmdletBinding()] 346 | Param() 347 | DynamicParam 348 | { 349 | $Drive = @{ 350 | Name = 'Drive' 351 | Type = [array] 352 | Position = 0 353 | Mandatory = $true 354 | ValidateSet = ([System.IO.DriveInfo]::GetDrives()).Name 355 | ParameterSetName = 'Drive' 356 | } 357 | 358 | $DriveType = @{ 359 | Name = 'DriveType' 360 | Type = [array] 361 | Position = 0 362 | Mandatory = $true 363 | ValidateSet = [System.Enum]::GetNames('System.IO.DriveType') 364 | ParameterSetName = 'DriveType' 365 | } 366 | 367 | # Create dictionary 368 | $DynamicParameters = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary 369 | 370 | # Add new dynamic parameter to dictionary 371 | New-DynamicParameter @Drive -Dictionary $DynamicParameters 372 | 373 | # Add another dynamic parameter to dictionary, only if today is not a Friday 374 | if((Get-Date).DayOfWeek -ne [DayOfWeek]::Friday) 375 | { 376 | New-DynamicParameter @DriveType -Dictionary $DynamicParameters 377 | } 378 | 379 | # Return dictionary with dynamic parameters 380 | $DynamicParameters 381 | } 382 | Process 383 | { 384 | # Dynamic parameters don't have corresponding variables created, 385 | # you need to call New-DynamicParameter with CreateVariables switch to fix that. 386 | New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters 387 | 388 | if($Drive) 389 | { 390 | $Filter = {$Drive -contains $_.Name} 391 | } 392 | elseif($DriveType) 393 | { 394 | $Filter = {$DriveType -contains $_.DriveType} 395 | } 396 | 397 | $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object $Filter 398 | $DriveInfo | 399 | ForEach-Object { 400 | if(!$_.TotalFreeSpace) 401 | { 402 | $FreePct = 0 403 | } 404 | else 405 | { 406 | $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2) 407 | } 408 | New-Object -TypeName psobject -Property @{ 409 | Drive = $_.Name 410 | DriveType = $_.DriveType 411 | 'Free(%)' = $FreePct 412 | } 413 | } 414 | } 415 | } 416 | #> 417 | Function New-DynamicParameter { 418 | [CmdletBinding(PositionalBinding = $false, DefaultParameterSetName = 'DynamicParameter')] 419 | Param 420 | ( 421 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 422 | [ValidateNotNullOrEmpty()] 423 | [string]$Name, 424 | 425 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 426 | [System.Type]$Type = [int], 427 | 428 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 429 | [string[]]$Alias, 430 | 431 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 432 | [switch]$Mandatory, 433 | 434 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 435 | [int]$Position, 436 | 437 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 438 | [string]$HelpMessage, 439 | 440 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 441 | [switch]$DontShow, 442 | 443 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 444 | [switch]$ValueFromPipeline, 445 | 446 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 447 | [switch]$ValueFromPipelineByPropertyName, 448 | 449 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 450 | [switch]$ValueFromRemainingArguments, 451 | 452 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 453 | [string]$ParameterSetName = '__AllParameterSets', 454 | 455 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 456 | [switch]$AllowNull, 457 | 458 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 459 | [switch]$AllowEmptyString, 460 | 461 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 462 | [switch]$AllowEmptyCollection, 463 | 464 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 465 | [switch]$ValidateNotNull, 466 | 467 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 468 | [switch]$ValidateNotNullOrEmpty, 469 | 470 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 471 | [ValidateCount(2,2)] 472 | [int[]]$ValidateCount, 473 | 474 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 475 | [ValidateCount(2,2)] 476 | [int[]]$ValidateRange, 477 | 478 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 479 | [ValidateCount(2,2)] 480 | [int[]]$ValidateLength, 481 | 482 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 483 | [ValidateNotNullOrEmpty()] 484 | [string]$ValidatePattern, 485 | 486 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 487 | [ValidateNotNullOrEmpty()] 488 | [scriptblock]$ValidateScript, 489 | 490 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 491 | [ValidateNotNullOrEmpty()] 492 | [string[]]$ValidateSet, 493 | 494 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DynamicParameter')] 495 | [ValidateNotNullOrEmpty()] 496 | [ValidateScript({ 497 | if(!($_ -is [System.Management.Automation.RuntimeDefinedParameterDictionary])) 498 | { 499 | Throw 'Dictionary must be a System.Management.Automation.RuntimeDefinedParameterDictionary object' 500 | } 501 | $true 502 | })] 503 | $Dictionary = $false, 504 | 505 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] 506 | [switch]$CreateVariables, 507 | 508 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'CreateVariables')] 509 | [ValidateNotNullOrEmpty()] 510 | [ValidateScript({ 511 | # System.Management.Automation.PSBoundParametersDictionary is an internal sealed class, 512 | # so one can't use PowerShell's '-is' operator to validate type. 513 | if($_.GetType().Name -ne 'PSBoundParametersDictionary') 514 | { 515 | Throw 'BoundParameters must be a System.Management.Automation.PSBoundParametersDictionary object' 516 | } 517 | $true 518 | })] 519 | $BoundParameters 520 | ) 521 | 522 | Begin 523 | { 524 | Write-Verbose 'Creating new dynamic parameters dictionary' 525 | $InternalDictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary 526 | 527 | Write-Verbose 'Getting common parameters' 528 | function _temp { [CmdletBinding()] Param() } 529 | $CommonParameters = (Get-Command _temp).Parameters.Keys 530 | } 531 | 532 | Process 533 | { 534 | if($CreateVariables) 535 | { 536 | Write-Verbose 'Creating variables from bound parameters' 537 | Write-Debug 'Picking out bound parameters that are not in common parameters set' 538 | $BoundKeys = $BoundParameters.Keys | Where-Object { $CommonParameters -notcontains $_ } 539 | 540 | foreach($Parameter in $BoundKeys) 541 | { 542 | Write-Debug "Setting existing variable for dynamic parameter '$Parameter' with value '$($BoundParameters.$Parameter)'" 543 | Set-Variable -Name $Parameter -Value $BoundParameters.$Parameter -Scope 1 -Force 544 | } 545 | } 546 | else 547 | { 548 | Write-Verbose 'Looking for cached bound parameters' 549 | Write-Debug 'More info: https://beatcracker.wordpress.com/2014/12/18/psboundparameters-pipeline-and-the-valuefrompipelinebypropertyname-parameter-attribute' 550 | $StaleKeys = @() 551 | $StaleKeys = $PSBoundParameters.GetEnumerator() | 552 | ForEach-Object { 553 | if($_.Value.PSobject.Methods.Name -match '^Equals$') 554 | { 555 | # If object has Equals, compare bound key and variable using it 556 | if(!$_.Value.Equals((Get-Variable -Name $_.Key -ValueOnly -Scope 0))) 557 | { 558 | $_.Key 559 | } 560 | } 561 | else 562 | { 563 | # If object doesn't has Equals (e.g. $null), fallback to the PowerShell's -ne operator 564 | if($_.Value -ne (Get-Variable -Name $_.Key -ValueOnly -Scope 0)) 565 | { 566 | $_.Key 567 | } 568 | } 569 | } 570 | if($StaleKeys) 571 | { 572 | [string[]]"Found $($StaleKeys.Count) cached bound parameters:" + $StaleKeys | Write-Debug 573 | Write-Verbose 'Removing cached bound parameters' 574 | $StaleKeys | ForEach-Object {[void]$PSBoundParameters.Remove($_)} 575 | } 576 | 577 | # Since we rely solely on $PSBoundParameters, we don't have access to default values for unbound parameters 578 | Write-Verbose 'Looking for unbound parameters with default values' 579 | 580 | Write-Debug 'Getting unbound parameters list' 581 | $UnboundParameters = (Get-Command -Name ($PSCmdlet.MyInvocation.InvocationName)).Parameters.GetEnumerator() | 582 | # Find parameters that are belong to the current parameter set 583 | Where-Object { $_.Value.ParameterSets.Keys -contains $PsCmdlet.ParameterSetName } | 584 | Select-Object -ExpandProperty Key | 585 | # Find unbound parameters in the current parameter set 586 | Where-Object { $PSBoundParameters.Keys -notcontains $_ } 587 | 588 | # Even if parameter is not bound, corresponding variable is created with parameter's default value (if specified) 589 | Write-Debug 'Trying to get variables with default parameter value and create a new bound parameter''s' 590 | $tmp = $null 591 | foreach($Parameter in $UnboundParameters) 592 | { 593 | $DefaultValue = Get-Variable -Name $Parameter -ValueOnly -Scope 0 594 | if(!$PSBoundParameters.TryGetValue($Parameter, [ref]$tmp) -and $DefaultValue) 595 | { 596 | $PSBoundParameters.$Parameter = $DefaultValue 597 | Write-Debug "Added new parameter '$Parameter' with value '$DefaultValue'" 598 | } 599 | } 600 | 601 | if($Dictionary) 602 | { 603 | Write-Verbose 'Using external dynamic parameter dictionary' 604 | $DPDictionary = $Dictionary 605 | } 606 | else 607 | { 608 | Write-Verbose 'Using internal dynamic parameter dictionary' 609 | $DPDictionary = $InternalDictionary 610 | } 611 | 612 | Write-Verbose "Creating new dynamic parameter: $Name" 613 | 614 | # Shortcut for getting local variables 615 | $GetVar = {Get-Variable -Name $_ -ValueOnly -Scope 0} 616 | 617 | # Strings to match attributes and validation arguments 618 | $AttributeRegex = '^(Mandatory|Position|ParameterSetName|DontShow|HelpMessage|ValueFromPipeline|ValueFromPipelineByPropertyName|ValueFromRemainingArguments)$' 619 | $ValidationRegex = '^(AllowNull|AllowEmptyString|AllowEmptyCollection|ValidateCount|ValidateLength|ValidatePattern|ValidateRange|ValidateScript|ValidateSet|ValidateNotNull|ValidateNotNullOrEmpty)$' 620 | $AliasRegex = '^Alias$' 621 | 622 | Write-Debug 'Creating new parameter''s attirubutes object' 623 | $ParameterAttribute = New-Object -TypeName System.Management.Automation.ParameterAttribute 624 | 625 | Write-Debug 'Looping through the bound parameters, setting attirubutes...' 626 | switch -regex ($PSBoundParameters.Keys) 627 | { 628 | $AttributeRegex 629 | { 630 | Try 631 | { 632 | $ParameterAttribute.$_ = . $GetVar 633 | Write-Debug "Added new parameter attribute: $_" 634 | } 635 | Catch 636 | { 637 | $_ 638 | } 639 | continue 640 | } 641 | } 642 | 643 | if($DPDictionary.Keys -contains $Name) 644 | { 645 | Write-Verbose "Dynamic parameter '$Name' already exist, adding another parameter set to it" 646 | $DPDictionary.$Name.Attributes.Add($ParameterAttribute) 647 | } 648 | else 649 | { 650 | Write-Verbose "Dynamic parameter '$Name' doesn't exist, creating" 651 | 652 | Write-Debug 'Creating new attribute collection object' 653 | $AttributeCollection = New-Object -TypeName Collections.ObjectModel.Collection[System.Attribute] 654 | 655 | Write-Debug 'Looping through bound parameters, adding attributes' 656 | switch -regex ($PSBoundParameters.Keys) 657 | { 658 | $ValidationRegex 659 | { 660 | Try 661 | { 662 | $ParameterOptions = New-Object -TypeName "System.Management.Automation.${_}Attribute" -ArgumentList (. $GetVar) -ErrorAction Stop 663 | $AttributeCollection.Add($ParameterOptions) 664 | Write-Debug "Added attribute: $_" 665 | } 666 | Catch 667 | { 668 | $_ 669 | } 670 | continue 671 | } 672 | 673 | $AliasRegex 674 | { 675 | Try 676 | { 677 | $ParameterAlias = New-Object -TypeName System.Management.Automation.AliasAttribute -ArgumentList (. $GetVar) -ErrorAction Stop 678 | $AttributeCollection.Add($ParameterAlias) 679 | Write-Debug "Added alias: $_" 680 | continue 681 | } 682 | Catch 683 | { 684 | $_ 685 | } 686 | } 687 | } 688 | 689 | Write-Debug 'Adding attributes to the attribute collection' 690 | $AttributeCollection.Add($ParameterAttribute) 691 | 692 | Write-Debug 'Finishing creation of the new dynamic parameter' 693 | $Parameter = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameter -ArgumentList @($Name, $Type, $AttributeCollection) 694 | 695 | Write-Debug 'Adding dynamic parameter to the dynamic parameter dictionary' 696 | $DPDictionary.Add($Name, $Parameter) 697 | } 698 | } 699 | } 700 | 701 | End 702 | { 703 | if(!$CreateVariables -and !$Dictionary) 704 | { 705 | Write-Verbose 'Writing dynamic parameter dictionary to the pipeline' 706 | $DPDictionary 707 | } 708 | } 709 | } -------------------------------------------------------------------------------- /New-GitSvnAuthorsFile.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Generate authors file for SVN to Git migration. 4 | Can map SVN authors to domain accounts and get full names and emails from Active Directiry. 5 | 6 | .Description 7 | Generate authors file for one or more SVN repositories. 8 | Can map SVN authors to domain accounts and get full names and emails from Active Directiry 9 | Requires Subversion binaries and Get-SvnAuthor function: 10 | https://github.com/beatcracker/Powershell-Misc/blob/master/Get-SvnAuthor.ps1 11 | 12 | .Notes 13 | Author: beatcracker (http://beatcracker.wordpress.com, https://github.com/beatcracker) 14 | License: Microsoft Public License (http://opensource.org/licenses/MS-PL) 15 | 16 | .Component 17 | Requires Subversion binaries and Get-SvnAuthor function: 18 | https://github.com/beatcracker/Powershell-Misc/blob/master/Get-SvnAuthor.ps1 19 | 20 | .Parameter Url 21 | This parameter is required. 22 | 23 | An array of strings representing URLs to the SVN repositories. 24 | 25 | .Parameter Path 26 | This parameter is optional. 27 | 28 | A string representing path, where to create authors file. 29 | If not specified, new authors file will be created in the script directory. 30 | 31 | .Parameter ShowOnly 32 | This parameter is optional. 33 | If this switch is specified, no file will be created and script will output collection of author names and emails. 34 | 35 | .Parameter QueryActiveDirectory 36 | This parameter is optional. 37 | 38 | A switch indicating whether or not to query Active Directory for author full name and email. 39 | Supports the following formats for SVN author name: john, domain\john, john@domain 40 | 41 | .Parameter User 42 | This parameter is optional. 43 | 44 | A string specifying username for SVN repository. 45 | 46 | .Parameter Password 47 | This parameter is optional. 48 | 49 | A string specifying password for SVN repository. 50 | 51 | .Parameter SvnPath 52 | This parameter is optional. 53 | 54 | A string specifying path to the svn.exe. Use it if Subversion binaries is not in your path variable, or you wish to use specific version. 55 | 56 | .Example 57 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' 58 | 59 | Description 60 | ----------- 61 | Create authors file for SVN repository http://svnserver/svn/project. 62 | New authors file will be created in the script directory. 63 | 64 | .Example 65 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' -QueryActiveDirectory 66 | 67 | Description 68 | ----------- 69 | Create authors file for SVN repository http://svnserver/svn/project. 70 | Map SVN authors to domain accounts and get full names and emails from Active Directiry. 71 | New authors file will be created in the script directory. 72 | 73 | .Example 74 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' -ShowOnly 75 | 76 | Description 77 | ----------- 78 | Create authors list for SVN repository http://svnserver/svn/project. 79 | No authors file will be created, instead script will return collection of objects. 80 | 81 | .Example 82 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' -Path c:\authors.txt 83 | 84 | Description 85 | ----------- 86 | Create authors file for SVN repository http://svnserver/svn/project. 87 | New authors file will be created as c:\authors.txt 88 | 89 | .Example 90 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' -User john -Password doe 91 | 92 | Description 93 | ----------- 94 | Create authors file for SVN repository http://svnserver/svn/project using username and password. 95 | New authors file will be created in the script directory. 96 | 97 | .Example 98 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project' -SvnPath 'C:\Program Files (x86)\VisualSVN Server\bin\svn.exe' 99 | 100 | Description 101 | ----------- 102 | Create authors file for SVN repository http://svnserver/svn/project using custom svn.exe binary. 103 | New authors file will be created in the script directory. 104 | 105 | .Example 106 | New-GitSvnAuthorsFile -Url 'http://svnserver/svn/project_1', 'http://svnserver/svn/project_2' 107 | 108 | Description 109 | ----------- 110 | Create authors file for two SVN repositories: http://svnserver/svn/project_1 and http://svnserver/svn/project_2. 111 | New authors file will be created in the script directory. 112 | 113 | .Example 114 | 'http://svnserver/svn/project_1', 'http://svnserver/svn/project_2' | New-GitSvnAuthorsFile 115 | 116 | Description 117 | ----------- 118 | Create authors file for two SVN repositories: http://svnserver/svn/project_1 and http://svnserver/svn/project_2. 119 | New authors file will be created in the script directory. 120 | #> 121 | [CmdletBinding(DefaultParameterSetName = 'Save')] 122 | Param 123 | ( 124 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 125 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 126 | [string[]]$Url, 127 | 128 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 129 | [ValidateScript({ 130 | $ParentFolder = Split-Path -LiteralPath $_ 131 | if(!(Test-Path -LiteralPath $ParentFolder -PathType Container)) 132 | { 133 | throw "Folder doesn't exist: $ParentFolder" 134 | } 135 | else 136 | { 137 | $true 138 | } 139 | })] 140 | [ValidateNotNullOrEmpty()] 141 | [string]$Path = (Join-Path -Path (Split-Path -Path $script:MyInvocation.MyCommand.Path) -ChildPath 'authors'), 142 | 143 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 144 | [switch]$ShowOnly, 145 | 146 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 147 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 148 | [switch]$QueryActiveDirectory, 149 | 150 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 151 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 152 | [string]$User, 153 | 154 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 155 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 156 | [string]$Password, 157 | 158 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Save')] 159 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Show')] 160 | [string]$SvnPath 161 | ) 162 | 163 | # Dotsource 'Get-SvnAuthor' function: 164 | # https://github.com/beatcracker/Powershell-Misc/blob/master/Get-SvnAuthor.ps1 165 | $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path 166 | . (Join-Path -Path $ScriptDir -ChildPath 'Get-SvnAuthor.ps1') 167 | 168 | # Strip extra parameters or splatting will fail 169 | $Param = @{} + $PSBoundParameters 170 | 'ShowOnly', 'QueryActiveDirectory', 'Path' | ForEach-Object {$Param.Remove($_)} 171 | 172 | # Get authors in SVN repo 173 | $Names = Get-SvnAuthor @Param 174 | [System.Collections.SortedList]$ret = @{} 175 | 176 | # Exit, if no authors found 177 | if(!$Names) 178 | { 179 | Exit 180 | } 181 | 182 | # Find full name and email for every author 183 | foreach($name in $Names) 184 | { 185 | $Email = '' 186 | 187 | if($QueryActiveDirectory) 188 | { 189 | # Get account name from commit author name in any of the following formats: 190 | # john, domain\john, john@domain 191 | $Local:tmp = $name -split '(@|\\)' 192 | switch ($Local:tmp.Count) 193 | { 194 | 1 { $SamAccountName = $Local:tmp[0] ; break } 195 | 3 { 196 | if($Local:tmp[1] -eq '\') 197 | { 198 | [array]::Reverse($Local:tmp) 199 | } 200 | 201 | $SamAccountName = $Local:tmp[0] 202 | break 203 | } 204 | default {$SamAccountName = $null} 205 | } 206 | 207 | # Lookup account details 208 | if($SamAccountName) 209 | { 210 | $UserProps = ([adsisearcher]"(samaccountname=$SamAccountName)").FindOne().Properties 211 | 212 | if($UserProps) 213 | { 214 | Try 215 | { 216 | $Email = '{0} <{1}>' -f $UserProps.displayname[0], $UserProps.mail[0] 217 | } 218 | Catch{} 219 | } 220 | } 221 | } 222 | 223 | $ret += @{$name = $Email} 224 | } 225 | 226 | if($ShowOnly) 227 | { 228 | $ret 229 | } 230 | else 231 | { 232 | # Use System.IO.StreamWriter to write a file with Unix newlines. 233 | # It's also significally faster then Add\Set-Content Cmdlets. 234 | Try 235 | { 236 | #StreamWriter Constructor (String, Boolean, Encoding): http://msdn.microsoft.com/en-us/library/f5f5x7kt.aspx 237 | $StreamWriter = New-Object -TypeName System.IO.StreamWriter -ArgumentList $Path, $false, ([System.Text.Encoding]::ASCII) 238 | } 239 | Catch 240 | { 241 | throw "Can't create file: $Path" 242 | } 243 | $StreamWriter.NewLine = "`n" 244 | 245 | foreach($item in $ret.GetEnumerator()) 246 | { 247 | $Local:tmp = '{0} = {1}' -f $item.Key, $item.Value 248 | $StreamWriter.WriteLine($Local:tmp) 249 | } 250 | 251 | $StreamWriter.Flush() 252 | $StreamWriter.Close() 253 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Miscellaneous PowerShell goodies 2 | 3 | ## Table of Contents 4 | 5 | - [How to use Functions\Scripts](#how-to-use-functionsscripts) 6 | - [In PowerShell console\PowerShell ISE script pane](#in-powershell-consolepowershell-ise-script-pane) 7 | - [In your own script](#in-your-own-script) 8 | - [Functions](#functions) 9 | - [Get-TerminologyTranslation](#get-terminologytranslation) 10 | - [Split-CommandLine](#split-commandline) 11 | - [Import-Component](#import-component) 12 | - [New-DynamicParameter](#new-dynamicparameter) 13 | - [Get-SvnAuthor](#get-svnauthor) 14 | - [Step-Dictionary](#step-dictionary) 15 | - [Get-SpecialFolderPath](#get-specialfolderpath) 16 | - [Start-ConsoleProcess](#start-consoleprocess) 17 | - [Remove-ComObject](#remove-comobject) 18 | - [Use-ServiceAccount](#use-serviceaccount) 19 | - [Use-Object](#use-object) 20 | - [Add-ClusterMsmqRole](#add-clustermsmqrole) 21 | - [ConvertTo-ZabbixJson](#convertto-zabbixjson) 22 | - [Write-Host](#write-host) 23 | - [Scripts](#scripts) 24 | - [New-GitSvnAuthorsFile](#new-gitsvnauthorsfile) 25 | 26 | ## How to use Functions\Scripts 27 | 28 | The best way to use provided functions is a [dot-sourcing](http://ss64.com/ps/source.html). 29 | 30 | Dot-sourcing runs a script file in the current scope so that any functions, aliases, and variables that the script file creates are added to the current scope. 31 | 32 | ### In PowerShell console\PowerShell ISE script pane 33 | 34 | *Note the space between first dot and path!* 35 | 36 | ```powershell 37 | . c:\scripts\Get-TerminologyTranslation.ps1 38 | ``` 39 | 40 | Or, navigate to the folder, where you downloaded script file 41 | 42 | ```none 43 | cd c:\scripts 44 | ``` 45 | 46 | And then type: 47 | 48 | ```powershell 49 | . .\Get-TerminologyTranslation.ps1 50 | ``` 51 | 52 | To verify, that script file is loaded, try to view help for the loaded function. The function name is usually the same as script file name (without extension), if not stated otherwise in readme. 53 | 54 | ```powershell 55 | Get-Help -Name Get-TerminologyTranslation -Full 56 | ``` 57 | 58 | Congratulations, you can now make use of newly added function! 59 | 60 | ```powershell 61 | 'Windows Update' | Get-TerminologyTranslation -From 'en-us' -To 'ru-ru' -Source Terms 62 | 63 | ``` 64 | 65 | ### In your own script 66 | 67 | *Note the space between first dot and path!* 68 | 69 | Dot-source from arbitrary location before calling the function: 70 | 71 | ```powershell 72 | . c:\scripts\Get-TerminologyTranslation.ps1 73 | 'Windows Update' | Get-TerminologyTranslation -From 'en-us' -To 'ru-ru' -Source Terms 74 | ``` 75 | 76 | Or put it alongside with your script, get path to the script folder programmatically, and then dot-source: 77 | 78 | ```powershell 79 | $ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path 80 | . (Join-Path -Path $ScriptDir -ChildPath 'Get-TerminologyTranslation.ps1') 81 | 'Windows Update' | Get-TerminologyTranslation -From 'en-us' -To 'ru-ru' -Source Terms 82 | 83 | ``` 84 | 85 | ## Functions 86 | 87 | ### [Get-TerminologyTranslation](Get-TerminologyTranslation.ps1) 88 | 89 | Enables user to look up terminology translations and user-interface translations from actual Microsoft products via [Microsoft Terminology Service API](http://www.microsoft.com/Language/en-US/Microsoft-Terminology-API.aspx). For details see [ Terminology Service API SDK PDF](http://download.microsoft.com/download/1/5/D/15D3DDC6-7403-4366-BE99-AF5247ADEF1C/Microsoft-Terminology-API-SDK.pdf). 90 | 91 | #### Features 92 | 93 | - Any-to-any language translation searches, e.g. Japanese to/from French or any other language combination. 94 | - Filter searches with string case and hotkey sensitivity. 95 | - Filter searches by product name and version. 96 | - Get list of languages supported by the Terminology Service API. 97 | - Get list of products supported by the Terminology Service API. 98 | - Full comment-based help and usage examples. 99 | 100 | #### Usage examples 101 | 102 | - Ever wonder how [Cherokee](http://en.wikipedia.org/wiki/Cherokee) spell `Start menu`? 103 | 104 | ```powershell 105 | 'Start menu' | Get-TerminologyTranslation -From 'en-us' -To 'chr-cher-us' -Source Both 106 | 107 | ᎠᏂᎩᏍᏙᏗ ᏗᏑᏰᏍᏗᎢ 108 | ``` 109 | 110 | - What is `Control panel` in French? 111 | 112 | ```powershell 113 | 'Control panel' | Get-TerminologyTranslation -From 'en-us' -To 'fr-fr' -Source Both 114 | 115 | Panneau de configuration 116 | ``` 117 | 118 | - How about `Fatal Error` in German? 119 | 120 | ```powershell 121 | 'Fatal error' | Get-TerminologyTranslation -From 'en-us' -To 'de-de' -Source Both 122 | 123 | Schwerwiegender Fehler 124 | ``` 125 | 126 | - You've got a cyrillic SharePoint installation and wonder where the hell is `Application Management` in SharePoint Central Administration? 127 | 128 | ```powershell 129 | 'Application Management' | Get-TerminologyTranslation -From 'en-us' -To 'ru-ru' -Source 'UiStrings' -Name 'SharePoint Server' 130 | 131 | Управление приложениями 132 | ``` 133 | 134 | ### [Split-CommandLine](Split-CommandLine.ps1) 135 | 136 | PowerShell version of [EchoArgs](https://devblogs.microsoft.com/scripting/solve-problems-with-external-command-lines-in-powershell/). This is the Cmdlet version of the code from the article [PowerShell and external commands done right](https://slai.github.io/posts/powershell-and-external-commands-done-right/). It can parse command-line arguments using Win32 API function [CommandLineToArgvW](https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) and echo command line arguments back out to the console for your review. 137 | 138 | #### Features 139 | 140 | - Parse arbitrary command-line, or if none specified, the command-line of the current PowerShell host. 141 | - Full comment-based help and usage examples. 142 | 143 | #### Usage examples 144 | 145 | - Parse user-specified command-line 146 | 147 | ```powershell 148 | '"c:\windows\notepad.exe" test.txt' | Split-CommandLine 149 | 150 | c:\windows\notepad.exe 151 | test.txt 152 | ``` 153 | 154 | - Parse command-line of the live process 155 | 156 | ```powershell 157 | Get-WmiObject Win32_Process -Filter "Name='notepad.exe'" | Split-CommandLine 158 | 159 | c:\windows\notepad.exe 160 | test.txt 161 | ``` 162 | 163 | ### [Import-Component](Import-Component.ps1) 164 | 165 | Bulk-import from folder any component, supported by PowerShell (script, module, source code, .Net assembly). 166 | 167 | #### Features 168 | 169 | - Supported components: 170 | - Script (.ps1) - imported using [Dot-Sourcing](http://ss64.com/ps/source.html). 171 | - Module - imported using [Import-Module](http://technet.microsoft.com/en-us/library/hh849725.aspx) cmdlet 172 | _This function will only try to import well-formed modules. A "well-formed" module is a module that is stored in a directory that has the same name as the base name of at least one file in the module directory. If a module is not well-formed, Windows PowerShell does not recognize it as a module. [More info](https://msdn.microsoft.com/en-us/library/dd878350.aspx)_ 173 | - Source code (.cs, .vb, .js) - imported using [Add-Type](http://technet.microsoft.com/en-us/library/hh849914.aspx) cmdlet 174 | - .Net assembly (.dll) - imported using [Add-Type](http://technet.microsoft.com/en-us/library/hh849914.aspx) cmdlet 175 | - Full comment-based help and usage examples. 176 | 177 | __WARNING: To import .PS1 scripts this function itself has to be dot-sourced!__ Example: 178 | 179 | *Note the space between first dot and function name!* 180 | 181 | ```powershell 182 | . Import-Component 'C:\PsLib' 183 | ``` 184 | 185 | #### Usage examples 186 | 187 | - Import all supported components (`.ps1`, `module`, `.cs`, `.vb`, `.js`, `.dll`), recurse into subdirectories. Include only files with names without extension that match wildcards `MyScript*` and `*MyLib*`. Exclude files with names without extension that match `*_backup*` and `*_old*` wildcards. 188 | 189 | ```powershell 190 | . Import-Component 'C:\PsLib' -Recurse -Include 'MyScript*','*MyLib*' -Exclude '*_backup*','*_old*' 191 | ``` 192 | 193 | ### [New-DynamicParameter](New-DynamicParameter.ps1) 194 | 195 | Helper function to simplify creating [dynamic parameters](https://technet.microsoft.com/en-us/library/hh847743.aspx). 196 | Example use cases: 197 | 198 | - Include parameters only if your environment dictates it 199 | - Include parameters depending on the value of a user-specified parameter 200 | - Provide tab completion and intellisense for parameters, depending on the environment 201 | 202 | Credits to Justin Rich ([blog](http://jrich523.wordpress.com), [GitHub](https://github.com/jrich523)) and Warren F. ([blog](http://ramblingcookiemonster.github.io), [GitHub](https://github.com/RamblingCookieMonster)) for their initial code and inspiration: 203 | 204 | - [New-DynamicParam.ps1](https://github.com/RamblingCookieMonster/PowerShell/New-DynamicParam.ps1) 205 | - [Credentials and Dynamic Parameters](http://ramblingcookiemonster.wordpress.com/2014/11/27/quick-hits-credentials-and-dynamic-parameters/) 206 | - [PowerShell: Simple way to add dynamic parameters to advanced function](http://jrich523.wordpress.com/2013/05/30/powershell-simple-way-to-add-dynamic-parameters-to-advanced-function/) 207 | 208 | Credit to BM for alias and type parameters and their handling. 209 | 210 | #### Features 211 | 212 | - Create dynamic parameters for your functions on the fly. 213 | - Full comment-based help and usage examples. 214 | 215 | #### Usage examples 216 | 217 | - Create one dynamic parameter. This example illustrates the use of `New-DynamicParameter` to create a single dynamic parameter. The `Drive`'s parameter `ValidateSet` is populated with all available volumes on the computer for handy tab completion / intellisense. 218 | 219 | ```powershell 220 | function Get-FreeSpace { 221 | [CmdletBinding()] 222 | Param() 223 | DynamicParam { 224 | # Get drive names for ValidateSet attribute 225 | $DriveList = ([System.IO.DriveInfo]::GetDrives()).Name 226 | 227 | # Create new dynamic parameter 228 | New-DynamicParameter -Name Drive -ValidateSet $DriveList -Type ([array]) -Position 0 -Mandatory 229 | } 230 | 231 | Process { 232 | # Dynamic parameters don't have corresponding variables created, 233 | # you need to call New-DynamicParameter with CreateVariables switch to fix that. 234 | New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters 235 | 236 | $DriveInfo = [System.IO.DriveInfo]::GetDrives() | Where-Object {$Drive -contains $_.Name} 237 | $DriveInfo | 238 | ForEach-Object { 239 | if (!$_.TotalFreeSpace) { 240 | $FreePct = 0 241 | } else { 242 | $FreePct = [System.Math]::Round(($_.TotalSize / $_.TotalFreeSpace), 2) 243 | } 244 | New-Object -TypeName psobject -Property @{ 245 | Drive = $_.Name 246 | DriveType = $_.DriveType 247 | 'Free(%)' = $FreePct 248 | } 249 | } 250 | } 251 | } 252 | ``` 253 | 254 | ### [Get-SvnAuthor](Get-SvnAuthor.ps1) 255 | 256 | Get list of unique commit authors in one or more SVN repositories. Requires Subversion binaries. Can be used to create authors file for SVN to Git migrations. 257 | 258 | #### Features 259 | 260 | - Get list of unique commit authors in one or more SVN repositories. 261 | - Full comment-based help and usage examples. 262 | 263 | #### Usage examples 264 | 265 | - Get list of unique commit authors for SVN repository `http://svnserver/svn/project` 266 | 267 | ```powershell 268 | 'http://svnserver/svn/project' | Get-SvnAuthor 269 | 270 | John Doe 271 | Jane Doe 272 | ``` 273 | 274 | ### [Step-Dictionary](Step-Dictionary.ps1) 275 | 276 | Recursively walk through each item in a dictionary and execute scriptblock against lowest level keys. You can modify and remove lowest level keys while iterating over dictionary. 277 | 278 | #### Features 279 | 280 | - Recursively walk through each item in a dictionary and execute scriptblock against lowest level keys. 281 | - Full comment-based help and usage examples. 282 | 283 | #### Usage examples 284 | 285 | ```powershell 286 | $Dictionary = @{ 287 | Alfa = @{ 288 | Bravo = @{ 289 | Charlie = 'FooBar' 290 | Delta = 'BarFoo' 291 | Echo = 'FarBoo' 292 | } 293 | } 294 | } 295 | 296 | # Search for lowest level keys with name 'Charlie' and output their values 297 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key]} -Include 'Charlie' 298 | 299 | # Search for all lowest level keys except 'Charlie' and output their values 300 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key]} -Exclude 'Charlie' 301 | 302 | # Print each lowest level key name and value 303 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {"${key}: $($Dictionary[$key])" | Write-Host} 304 | 305 | # Set each lowest level key to random value 306 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key] = Get-Random} 307 | 308 | # Remove every lowest level key 309 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary.Remove($key)} 310 | ``` 311 | 312 | ### [Get-SpecialFolderPath](Get-SpecialFolderPath.ps1) 313 | 314 | Gets the path to the system special folder that is identified by the specified enumeration. On pre .NET 4.0 systems tries to map unknown [KNOWNFOLDERID](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx) to [CSIDLs](https://gist.github.com/beatcracker/4b154d46cc26776b50e7/raw/a317160dad57157f100e0f6e6d68c692c2bee7f1/ShlObj.h). This, for example allows to query for `ProgramFilesx86` directory when PowerShell is running in .Net 3.5, where [SpecialFolder enumeration](https://msdn.microsoft.com/en-us/library/system.environment.specialfolder.aspx) contains only `KNOWNFOLDERID` for `ProgramFiles`. 315 | 316 | #### Features 317 | 318 | - Gets the path to the system special folder that is identified by the specified enumeration (`CSIDL`, `KNOWNFOLDERID` or best match). 319 | - Full comment-based help and usage examples. 320 | 321 | #### Usage examples 322 | 323 | - Get folder paths for 'Favorites' and Desktop folders using both `CSIDL` and `KNOWNFOLDERID` 324 | 325 | ```powershell 326 | 'Favorites', 'CSIDL_DESKTOP' | Get-SpecialFolderPath 327 | Get-SpecialFolderPath -Name 'Favorites', 'CSIDL_DESKTOP' 328 | ``` 329 | 330 | - Get folder path for Desktop folder by `CSIDL` 331 | 332 | ```powershell 333 | Get-SpecialFolderPath -Csidl 'CSIDL_DESKTOP' 334 | ``` 335 | 336 | - Get folder path for 'Favorites' folder by `KNOWNFOLDERID` 337 | 338 | ```powershell 339 | Get-SpecialFolderPath -KnownFolderId 'Favorites' 340 | ``` 341 | 342 | - Get folder path for `NetHood` `KNOWNFOLDERID`. 343 | 344 | ```powershell 345 | Get-SpecialFolderPath -Name 'NetHood' 346 | ``` 347 | 348 | On my system, there is no `NetHood` `KNOWNFOLDERID` in SpecialFolder enumeration, so the function will fallback to `CSIDL` mapping. Example of verbose output in this situation: 349 | 350 | VERBOSE: Checking if [System.Environment]::GetFolderPath available 351 | VERBOSE: Result: True 352 | VERBOSE: No KnownFolderId for: 'NetHood', trying to map to CSIDL 353 | VERBOSE: KnownFolderId 'NetHood' is mapped to CSIDL(s): CSIDL_NETHOOD 354 | VERBOSE: Registering type: Shell32.Tools 355 | VERBOSE: Processing CSIDL(s): CSIDL_NETHOOD 356 | C:\Users\beatcracker\AppData\Roaming\Microsoft\Windows\Network Shortcuts 357 | 358 | ### [Start-ConsoleProcess](Start-ConsoleProcess.ps1) 359 | 360 | This function will start console executable, pipe any user-specified strings to it and capture `StandardOutput`/`StandardError` streams and `exit code`. 361 | 362 | #### Features 363 | 364 | - Returns object with following properties: 365 | - `StdOut` - array of strings captured from `StandardOutput` 366 | - `StdErr` - array of strings captured from `StandardError` 367 | - `ExitCode` - exit code set by executable 368 | - Full comment-based help and usage examples. 369 | 370 | #### Usage examples 371 | 372 | - Start `find.exe` and capture its output. Because no arguments specified, `find.exe` prints error to `StandardError` stream, which is captured by the function. 373 | 374 | ```powershell 375 | Start-ConsoleProcess -FilePath find 376 | 377 | StdOut StdErr ExitCode 378 | ------ ------ -------- 379 | {} {FIND: Parameter format not correct} 2 380 | ``` 381 | 382 | - Start `robocopy.exe` with arguments and capture its output. `Robocopy.exe` will mirror contents of the `C:\Src` folder to `C:\Dst` and print log to `StandardOutput` stream, which is captured by the function. 383 | 384 | ```powershell 385 | $Result = Start-ConsoleProcess -FilePath robocopy -ArgumentList 'C:\Src', 'C:\Dst', '/mir' 386 | $Result.StdOut 387 | 388 | ------------------------------------------------------------------------------- 389 | ROBOCOPY :: Robust File Copy for Windows 390 | ------------------------------------------------------------------------------- 391 | 392 | Started : 01 January 2016 y. 00:00:01 393 | Source : C:\Src\ 394 | Dest : C:\Dst\ 395 | 396 | Files : *.* 397 | 398 | Options : *.* /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /R:1000000 /W:30 399 | 400 | ------------------------------------------------------------------------------ 401 | 402 | 1 C:\Src\ 403 | New File 6 Readme.txt 404 | 0% 405 | 100% 406 | 407 | ------------------------------------------------------------------------------ 408 | 409 | Total Copied Skipped Mismatch FAILED Extras 410 | Dirs : 1 0 0 0 0 0 411 | Files : 1 1 0 0 0 0 412 | Bytes : 6 6 0 0 0 0 413 | Times : 0:00:00 0:00:00 0:00:00 0:00:00 414 | 415 | 416 | Speed : 103 Bytes/sec. 417 | Speed : 0.005 MegaBytes/min. 418 | Ended : 01 January 2016 y. 00:00:01 419 | ``` 420 | 421 | - Start `diskpart.exe`, pipe strings to its StandardInput and capture its output. `Diskpart.exe` will accept piped strings as if they were typed in the interactive session and list all disks and volumes on the PC. 422 | 423 | Note that running `diskpart` requires already elevated PowerShell console. Otherwise, you will recieve elevation request and `diskpart` will run, however, no strings would be piped to it. 424 | 425 | ```none 426 | $Result = 'list disk', 'list volume' | Start-ConsoleProcess -FilePath diskpart 427 | $Result.StdOut 428 | 429 | Microsoft DiskPart version 6.3.9600 430 | 431 | Copyright (C) 1999-2013 Microsoft Corporation. 432 | On computer: HAL9000 433 | 434 | DISKPART> 435 | Disk ### Status Size Free Dyn Gpt 436 | -------- ------------- ------- ------- --- --- 437 | Disk 0 Online 298 GB 0 B 438 | 439 | DISKPART> 440 | Volume ### Ltr Label Fs Type Size Status Info 441 | ---------- --- ----------- ----- ---------- ------- --------- -------- 442 | Volume 0 E DVD-ROM 0 B No Media 443 | Volume 1 C System NTFS Partition 100 GB Healthy System 444 | Volume 2 D Storage NTFS Partition 198 GB Healthy 445 | 446 | DISKPART> 447 | ``` 448 | 449 | ### [Remove-ComObject](Remove-ComObject.ps1) 450 | 451 | Release COM object and remove associated variable. COM object is released using [Marshal.FinalReleaseComObject](https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.finalreleasecomobject.aspx) method call. Optionally you can force garbage collection. 452 | 453 | #### Features 454 | 455 | - Release COM object and remove associated variable. 456 | - Full comment-based help and usage examples. 457 | 458 | #### Usage examples 459 | 460 | - Removes COM object stored in variable `$Ie` and variable itself. 461 | 462 | ```powershell 463 | # Create Internet Explorer COM object 464 | $Ie = New-Object -ComObject InternetExplorer.Application 465 | 466 | # ... do stuff ... 467 | 468 | # Remove Internet Explorer COM object 469 | Remove-ComObject -Name Ie 470 | ``` 471 | 472 | ### [Use-ServiceAccount](Use-ServiceAccount.ps1) 473 | 474 | Wrapper around Win32 API functions for managing (Group) Managed Service Accounts. Allows to test/add/remove (G)MSAs. 475 | 476 | See this post for more details: [Using Group Managed Service Accounts without Active Directory module](https://beatcracker.wordpress.com/2017/02/03/using-group-managed-service-accounts-without-active-directory-module/) 477 | 478 | Unlike it's counterparts in the 'Active Directory' module, which [require CredSSP](http://serverfault.com/questions/203123/unable-able-to-run-remote-powershell-using-active-directory) to be configured when used over PSRemoting, this function works with [resource-based Kerberos constrained delegation](https://blogs.technet.microsoft.com/ashleymcglone/2016/08/30/powershell-remoting-kerberos-double-hop-solved-securely/). 479 | 480 | #### Features 481 | 482 | - Test/Add/Remove (Group) Managed Service Accounts 483 | - Full comment-based help and usage examples. 484 | 485 | #### Usage examples 486 | 487 | - Test whether the specified standalone managed service account (sMSA) or group managed service account (gMSA) exists in the Netlogon store on the this server. 488 | 489 | ```powershell 490 | 'GMSA_Acount' | Use-ServiceAccount -Test 491 | ``` 492 | 493 | - Install Group Managed Service Account with SAM account name 'GMSA_Account' on the computer on which the cmdlet is run. 494 | 495 | ```powershell 496 | 'GMSA_Acount' | Use-ServiceAccount -Add 497 | ``` 498 | 499 | - Queries the specified service account from the local computer. 500 | 501 | ```powershell 502 | 'GMSA_Acount' | Use-ServiceAccount -Query 503 | ``` 504 | 505 | - Queries the specified service account from the local computer and return [MSA_INFO_STATE enumeration](https://msdn.microsoft.com/en-us/library/windows/desktop/dd894396.aspx) containing detailed information on (G)MSA state. 506 | 507 | ```powershell 508 | 'GMSA_Acount' | Use-ServiceAccount -Query -Detailed 509 | ``` 510 | 511 | ### [Use-Object](Use-Object.ps1) 512 | 513 | PowerShell-style version of C# `using` statement. 514 | 515 | I felt that C# syntax is no quite fit for PowerShell, so I've made a "pipelined" version. 516 | 517 | The object is passed via the pipeline and scriptblock is passed as parameter. 518 | The object is available to the scriptblock via $_ variable, similarly to `ForEach-Obect`. 519 | 520 | More details here: [Yet another Using statement](https://beatcracker.wordpress.com/2017/12/09/yet-another-using-statement/) 521 | 522 | #### Usage examples 523 | 524 | - Use `StreamWriter` to write text to file. Stream will be disposed and closed after scriptblock is executed. 525 | 526 | ```powershell 527 | New-Object -TypeName System.IO.StreamWriter -ArgumentList 'c:\foo.txt' | Use-Object {$_.WriteLine('BAR')} 528 | ``` 529 | 530 | - Use Internet Explorer to show website and release IE COM object afterwards. 531 | 532 | ```powershell 533 | New-Object -ComObject InternetExplorer.Application | Use-Object { 534 | $_.Visible = $true 535 | $_.navigate('https://bing.com') 536 | Start-Sleep -Seconds 10 537 | $_.Quit() 538 | } 539 | ``` 540 | 541 | ### [Add-ClusterMsmqRole](Add-ClusterMsmqRole.ps1) 542 | 543 | Creates clustered MSMQ role with correct group type and dependencies. Can optionally add services to the created group. 544 | 545 | See this post for more details: [Create clustered MSMQ role using PowerShell](https://beatcracker.wordpress.com/2018/08/12/create-clustered-msmq-role-using-powershell/) 546 | 547 | #### Usage examples 548 | 549 | - Create new MSMQ role with network name `MSMQ` and IP address `10.20.30.40` using `Cluster Disk 1` for shared storage. Start `MSMQ` group after it`s been created. 550 | 551 | ```powershell 552 | Add-ClusterMsmqRole -Name 'MSMQ' -Disk 'Cluster Disk 1' -StaticAddress '10.20.30.40' -Start 553 | ``` 554 | 555 | - Create new MSMQ role with network name `MSMQ` and IP address `10.20.30.40` using `Cluster Disk 1` for shared storage. Add windows service `SomeService` to `MSMQ` group. Do not start `MSMQ` group. 556 | 557 | ```powershell 558 | Add-ClusterMsmqRole -Name 'MSMQ' -Disk 'Cluster Disk 1' -StaticAddress '10.20.30.40' -Service 'SomeService' 559 | ``` 560 | 561 | ### [ConvertTo-ZabbixJson](ConvertTo-ZabbixJson.ps1) 562 | 563 | Convert an object to a JSON that can be used with [Zabbix low-level discovery](https://www.zabbix.com/documentation/3.4/manual/discovery/low_level_discovery). 564 | 565 | There are lot of Zabbix templates out there that use PowerShell for low-level discovery. Unfortunately, each an every one of them generates JSON [like this](https://github.com/vintagegamingsystems/Disk-Low-Level-Discovery-for-Physical-Disk-within-Windows-Performance-Monitoring-in-Zabbix-2.0/blob/master/get_disks.ps1): 566 | 567 | ```powershell 568 | $drives = Get-WmiObject win32_PerfFormattedData_PerfDisk_PhysicalDisk | ?{$_.name -ne "_Total"} | Select Name 569 | $idx = 1 570 | write-host "{" 571 | write-host " `"data`":[`n" 572 | foreach ($perfDrives in $drives) 573 | { 574 | if ($idx -lt $drives.Count) 575 | { 576 | $line= "{ `"{#DISKNUMLET}`" : `"" + $perfDrives.Name + "`" }," 577 | write-host $line 578 | } 579 | elseif ($idx -ge $drives.Count) 580 | { 581 | $line= "{ `"{#DISKNUMLET}`" : `"" + $perfDrives.Name + "`" }" 582 | write-host $line 583 | } 584 | $idx++; 585 | } 586 | write-host 587 | write-host " ]" 588 | write-host "}" 589 | ``` 590 | 591 | Please, use this function to easily generate Zabbix LLD-compatible JSON instead of trying to create it manually. Save the puppies! 592 | 593 | #### Usage examples 594 | 595 | - Converts `PhysicalDisk` object to Zabbix LLD JSON. 596 | 597 | ```powershell 598 | Get-WmiObject -Class win32_PerfFormattedData_PerfDisk_PhysicalDisk | 599 | Where-Object Name -ne '_Total' | 600 | Select-Object -Property Name | 601 | ConvertTo-ZabbixJson 602 | 603 | { 604 | "data": [ 605 | { 606 | "{#NAME}": "0 C:" 607 | }, 608 | { 609 | "{#NAME}": "1 D:" 610 | } 611 | ] 612 | } 613 | ``` 614 | 615 | ### [Write-Host](Write-Host.ps1) 616 | 617 | Write-Host but with ANSI colors! 618 | 619 | Drop-in Write-Host replacement that uses [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit) to render colors. Allows for colorized output in CI systems. 620 | 621 | ## Scripts 622 | 623 | ### [New-GitSvnAuthorsFile](New-GitSvnAuthorsFile.ps1) 624 | 625 | Generate authors file for one or more SVN repositories to assist SVN to Git migrations. Can map SVN authors to domain accounts and get full names and emails from Active Directory. Requires Subversion binaries and [Get-SvnAuthor](#get-svnauthor) function. 626 | 627 | #### Features 628 | 629 | - Generate authors file for one or more SVN repositories 630 | - Map SVN authors to domain accounts and get full names and emails from Active Directory 631 | - Full comment-based help and usage examples. 632 | 633 | #### Usage examples 634 | 635 | - Create authors file for SVN repository `http://svnserver/svn/project`. New authors file will be created as `c:\authors.txt` 636 | 637 | ```powershell 638 | 'http://svnserver/svn/project' | New-GitSvnAuthorsFile -Path c:\authors.txt 639 | 640 | ``` 641 | -------------------------------------------------------------------------------- /Remove-ComObject.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Release COM object and remove associated variable. 4 | 5 | .Description 6 | Release COM object and remove associated variable. 7 | COM object is released using [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject method call. 8 | Optionally you can force garbage collection. 9 | 10 | .Parameter Name 11 | Array of variables names, that contain COM objects. 12 | 13 | .Parameter Force 14 | Switch. Allows the function to remove a variable even if it is read-only. 15 | Note, that even using the Force parameter, the function cannot remove a constant. 16 | 17 | .Parameter ForceGC 18 | Switch. Force garbage collection after removing all COM objects and associated variables. 19 | 20 | .Example 21 | Remove-ComObject -Name Ie 22 | 23 | Removes COM object stored in variable $Ie and variable itself. 24 | 25 | Example: 26 | 27 | # Create Internet Explorer COM object 28 | $Ie = New-Object -ComObject InternetExplorer.Application 29 | 30 | # ... do stuff ... 31 | 32 | # Remove Internet Explorer COM object 33 | Remove-ComObject -Name Ie 34 | 35 | .Example 36 | 'Ie' | Remove-ComObject 37 | 38 | Removes COM object stored in variable $Ie and variable itself. 39 | Variable name is supplied via pipeline. 40 | 41 | .Example 42 | Remove-ComObject -Name Ie, Excel, Word 43 | 44 | Removes COM objects stored in variables $Ie, $Excel, and $Word and variables themselves. 45 | 46 | .Example 47 | 'Ie', 'Excel', 'Word' | Remove-ComObject 48 | 49 | Removes COM objects stored in variables $Ie, $Excel, and $Word and variables themselves. 50 | Variable names are supplied via pipeline. 51 | 52 | .Example 53 | Remove-ComObject -Name Ie -ForceGC 54 | 55 | Removes COM object stored in variable $Ie and variable itself. 56 | Force immediate garbage collection after variable and COM object has been removed. 57 | 58 | .Example 59 | Remove-ComObject -Name Ie -Force 60 | 61 | Removes COM object stored in variable $Ie and variable itself. 62 | Variable removed even if it's ReadOnly. 63 | 64 | Example: 65 | 66 | # Create Internet Explorer COM object 67 | New-Variable -Name Ie -Option ReadOnly -Value (New-Object -ComObject InternetExplorer.Application) 68 | 69 | # ... do stuff ... 70 | 71 | # Remove Internet Explorer COM object 72 | Remove-ComObject -Name Ie -Force 73 | #> 74 | function Remove-ComObject 75 | { 76 | [CmdletBinding()] 77 | Param 78 | ( 79 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 80 | [string[]]$Name, 81 | 82 | [switch]$Force, 83 | 84 | [switch]$ForceGC 85 | ) 86 | 87 | Process 88 | { 89 | foreach($var in $Name) 90 | { 91 | Write-Verbose 'Trying to release COM object' 92 | if 93 | ( 94 | [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject( 95 | [System.__ComObject](Get-Variable -Name $var -Scope 1 -ValueOnly -ErrorAction Stop) 96 | ) 97 | ) 98 | { 99 | Write-Error 'Failed to release COM object!' 100 | } 101 | 102 | Write-Verbose "Removing variable from parent scope: $var" 103 | Remove-Variable -Name $var -Scope 1 -Force:$Force 104 | } 105 | } 106 | 107 | End 108 | { 109 | if($ForceGC) 110 | { 111 | Write-Verbose 'Forcing garbage collection' 112 | [System.GC]::Collect() 113 | [System.GC]::WaitForPendingFinalizers() 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Split-CommandLine.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Parse command-line arguments using Win32 API CommandLineToArgvW function. 4 | 5 | .Link 6 | http://edgylogic.com/blog/powershell-and-external-commands-done-right/ 7 | 8 | .Description 9 | This is the Cmdlet version of the code from the article http://edgylogic.com/blog/powershell-and-external-commands-done-right. 10 | It can parse command-line arguments using Win32 API function CommandLineToArgvW . 11 | 12 | .Parameter CommandLine 13 | This parameter is optional. 14 | 15 | A string representing the command-line to parse. If not specified, the command-line of the current PowerShell host is used. 16 | 17 | .Example 18 | Split-CommandLine 19 | 20 | Description 21 | ----------- 22 | Get the command-line of the current PowerShell host, parse it and return arguments. 23 | 24 | .Example 25 | Split-CommandLine -CommandLine '"c:\windows\notepad.exe" test.txt' 26 | 27 | Description 28 | ----------- 29 | Parse user-specified command-line and return arguments. 30 | 31 | .Example 32 | '"c:\windows\notepad.exe" test.txt', '%SystemRoot%\system32\svchost.exe -k LocalServiceNetworkRestricted' | Split-CommandLine 33 | 34 | Description 35 | ----------- 36 | Parse user-specified command-line from pipeline input and return arguments. 37 | 38 | .Example 39 | Get-WmiObject Win32_Process -Filter "Name='notepad.exe'" | Split-CommandLine 40 | 41 | Description 42 | ----------- 43 | Parse user-specified command-line from property name of the pipeline object and return arguments. 44 | #> 45 | function Split-CommandLine 46 | { 47 | [CmdletBinding()] 48 | Param 49 | ( 50 | [Parameter(Mandatory=$false, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, Position=0)] 51 | [ValidateNotNullOrEmpty()] 52 | [string]$CommandLine 53 | ) 54 | 55 | Begin 56 | { 57 | $Kernel32Definition = @' 58 | [DllImport("kernel32")] 59 | public static extern IntPtr GetCommandLineW(); 60 | [DllImport("kernel32")] 61 | public static extern IntPtr LocalFree(IntPtr hMem); 62 | '@ 63 | $Kernel32 = Add-Type -MemberDefinition $Kernel32Definition -Name 'Kernel32' -Namespace 'Win32' -PassThru 64 | 65 | $Shell32Definition = @' 66 | [DllImport("shell32.dll", SetLastError = true)] 67 | public static extern IntPtr CommandLineToArgvW( 68 | [MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, 69 | out int pNumArgs); 70 | '@ 71 | $Shell32 = Add-Type -MemberDefinition $Shell32Definition -Name 'Shell32' -Namespace 'Win32' -PassThru 72 | 73 | if(!$CommandLine) 74 | { 75 | $CommandLine = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Kernel32::GetCommandLineW()) 76 | } 77 | } 78 | 79 | Process 80 | { 81 | $ParsedArgCount = 0 82 | $ParsedArgsPtr = $Shell32::CommandLineToArgvW($CommandLine, [ref]$ParsedArgCount) 83 | 84 | Try 85 | { 86 | $ParsedArgs = @(); 87 | 88 | 0..$ParsedArgCount | ForEach-Object { 89 | $ParsedArgs += [System.Runtime.InteropServices.Marshal]::PtrToStringUni( 90 | [System.Runtime.InteropServices.Marshal]::ReadIntPtr($ParsedArgsPtr, $_ * [IntPtr]::Size) 91 | ) 92 | } 93 | } 94 | Finally 95 | { 96 | $Kernel32::LocalFree($ParsedArgsPtr) | Out-Null 97 | } 98 | 99 | $ret = @() 100 | 101 | # -lt to skip the last item, which is a NULL ptr 102 | for ($i = 0; $i -lt $ParsedArgCount; $i += 1) { 103 | $ret += $ParsedArgs[$i] 104 | } 105 | 106 | return $ret 107 | } 108 | } -------------------------------------------------------------------------------- /Start-ConsoleProcess.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Launch console process, pipe strings to its StandardInput 4 | and get resulting StandardOutput/StandardError streams and exit code. 5 | 6 | .Description 7 | This function will start console executable, pipe any user-specified strings to it 8 | and capture StandardOutput/StandardError streams and exit code. 9 | It returns object with following properties: 10 | 11 | StdOut - array of strings captured from StandardOutput 12 | StdErr - array of strings captured from StandardError 13 | ExitCode - exit code set by executable 14 | 15 | .Parameter FilePath 16 | Path to the executable or its name. 17 | 18 | .Parameter ArgumentList 19 | Array of arguments for the executable. 20 | Passing arguments as an array allows to run even such unfriendly applications as robocopy. 21 | 22 | .Parameter InputObject 23 | Array of strings to be piped to the executable's StandardInput. 24 | This allows you to execute commands in interactive sessions of netsh and diskpart. 25 | 26 | .Example 27 | Start-ConsoleProcess -FilePath find 28 | 29 | Start find.exe and capture its output. 30 | Because no arguments specified, find.exe prints error to StandardError stream, 31 | which is captured by the function: 32 | 33 | StdOut StdErr ExitCode 34 | ------ ------ -------- 35 | {} {FIND: Parameter format not correct} 2 36 | 37 | .Example 38 | 'aaa', 'bbb', 'ccc' | Start-ConsoleProcess -FilePath find -ArgumentList '"aaa"' 39 | 40 | Start find.exe, pipe strings to its StandardInput and capture its output. 41 | Find.exe will attempt to find string "aaa" in StandardInput stream and 42 | print matches to StandardOutput stream, which is captured by the function: 43 | 44 | StdOut StdErr ExitCode 45 | ------ ------ -------- 46 | {aaa} {} 0 47 | 48 | .Example 49 | 'list disk', 'list volume' | Start-ConsoleProcess -FilePath diskpart 50 | 51 | Start diskpart.exe, pipe string to its StandardInput and capture its output. 52 | Diskpart.exe will accept piped strings as if they were typed in the interactive session 53 | and list all disks and volumes on the PC. 54 | 55 | Note that running diskpart requires already elevated PowerShell console. 56 | Otherwise, you will recieve elevation request and diskpart will run, 57 | however, no strings would be piped to it. 58 | 59 | Example: 60 | 61 | PS > $Result = 'list disk', 'list volume' | Start-ConsoleProcess -FilePath diskpart 62 | PS > $Result.StdOut 63 | 64 | Microsoft DiskPart version 6.3.9600 65 | 66 | Copyright (C) 1999-2013 Microsoft Corporation. 67 | On computer: HAL9000 68 | 69 | DISKPART> 70 | Disk ### Status Size Free Dyn Gpt 71 | -------- ------------- ------- ------- --- --- 72 | Disk 0 Online 298 GB 0 B 73 | 74 | DISKPART> 75 | Volume ### Ltr Label Fs Type Size Status Info 76 | ---------- --- ----------- ----- ---------- ------- --------- -------- 77 | Volume 0 E DVD-ROM 0 B No Media 78 | Volume 1 C System NTFS Partition 100 GB Healthy System 79 | Volume 2 D Storage NTFS Partition 198 GB Healthy 80 | 81 | DISKPART> 82 | 83 | .Example 84 | Start-ConsoleProcess -FilePath robocopy -ArgumentList 'C:\Src', 'C:\Dst', '/mir' 85 | 86 | Start robocopy.exe with arguments and capture its output. 87 | Robocopy.exe will mirror contents of the 'C:\Src' folder to 'C:\Dst' 88 | and print log to StandardOutput stream, which is captured by the function. 89 | 90 | Example: 91 | 92 | PS > $Result = Start-ConsoleProcess -FilePath robocopy -ArgumentList 'C:\Src', 'C:\Dst', '/mir' 93 | PS > $Result.StdOut 94 | 95 | ------------------------------------------------------------------------------- 96 | ROBOCOPY :: Robust File Copy for Windows 97 | ------------------------------------------------------------------------------- 98 | 99 | Started : 01 January 2016 y. 00:00:01 100 | Source : C:\Src\ 101 | Dest : C:\Dst\ 102 | 103 | Files : *.* 104 | 105 | Options : *.* /S /E /DCOPY:DA /COPY:DAT /PURGE /MIR /R:1000000 /W:30 106 | 107 | ------------------------------------------------------------------------------ 108 | 109 | 1 C:\Src\ 110 | New File 6 Readme.txt 111 | 0% 112 | 100% 113 | 114 | ------------------------------------------------------------------------------ 115 | 116 | Total Copied Skipped Mismatch FAILED Extras 117 | Dirs : 1 0 0 0 0 0 118 | Files : 1 1 0 0 0 0 119 | Bytes : 6 6 0 0 0 0 120 | Times : 0:00:00 0:00:00 0:00:00 0:00:00 121 | 122 | 123 | Speed : 103 Bytes/sec. 124 | Speed : 0.005 MegaBytes/min. 125 | Ended : 01 January 2016 y. 00:00:01 126 | #> 127 | function Start-ConsoleProcess 128 | { 129 | Param 130 | ( 131 | [Parameter(Mandatory = $true)] 132 | [ValidateNotNullOrEmpty()] 133 | [string]$FilePath, 134 | 135 | [string[]]$ArgumentList, 136 | 137 | [Parameter(ValueFromPipeline = $true)] 138 | [string[]]$InputObject 139 | ) 140 | 141 | End 142 | { 143 | if($Input) 144 | { 145 | # Collect all pipeline input 146 | # http://www.powertheshell.com/input_psv3/ 147 | $StdIn = @($Input) 148 | } 149 | else 150 | { 151 | $StdIn = $InputObject 152 | } 153 | 154 | try 155 | { 156 | "Starting process: $FilePath", "Redirect StdIn: $([bool]$StdIn.Count)", "Arguments: $ArgumentList" | Write-Verbose 157 | 158 | if($StdIn.Count) 159 | { 160 | $Output = $StdIn | & $FilePath $ArgumentList 2>&1 161 | } 162 | else 163 | { 164 | $Output = & $FilePath $ArgumentList 2>&1 165 | } 166 | } 167 | catch 168 | { 169 | throw $_ 170 | } 171 | 172 | Write-Verbose 'Finished, processing output' 173 | 174 | $StdOut = New-Object -TypeName System.Collections.Generic.List``1[String] 175 | $StdErr = New-Object -TypeName System.Collections.Generic.List``1[String] 176 | 177 | foreach($item in $Output) 178 | { 179 | # Data from StdOut will be strings, while StdErr produces 180 | # System.Management.Automation.ErrorRecord objects. 181 | # http://stackoverflow.com/a/33002914/4424236 182 | if($item.Exception.Message) 183 | { 184 | $StdErr.Add($item.Exception.Message) 185 | } 186 | else 187 | { 188 | $StdOut.Add($item) 189 | } 190 | } 191 | 192 | Write-Verbose 'Returning result' 193 | New-Object -TypeName PSCustomObject -Property @{ 194 | ExitCode = $LASTEXITCODE 195 | StdOut = $StdOut.ToArray() 196 | StdErr = $StdErr.ToArray() 197 | } | Select-Object -Property StdOut, StdErr, ExitCode 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Step-Dictionary.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Recursively walk through each item in a dictionary and execute scriptblock against lowest level keys. 4 | 5 | .Description 6 | Recursively walk through each item in a dictionary and execute scriptblock against lowest level keys. 7 | You can modify and remove lowest level keys while iterating over dictionary. 8 | 9 | .Parameter Dictionary 10 | Dictionary to iterate over. Must implement 'IDictionary' interface (hashtable, various .NET dictionaries). 11 | 12 | .Parameter Scriptblock 13 | Scriptblock to execute. To access dictionary's key and it's value, two variables are exposed to the scriptblock: 14 | 15 | $Dictionary - current dictionary entry 16 | $key - key name 17 | 18 | .Parameter Include 19 | Execute scriptblock only if lowest level key name matches specified wildcard. Accepts array of wildcards. 20 | 21 | .Parameter Exclude 22 | Do not execute scriptblock if lowest level key name matches specified wildcard. Accepts array of wildcards. 23 | 24 | .Parameter Depth 25 | Maximum nested node depth. Default value is [Int32]::MaxValue (2147483647). 26 | Keys with greater depth are not processed. 27 | 28 | .Parameter PassThru 29 | Return resulting dictionary. 30 | 31 | .Parameter CurrentDepth 32 | Internal parameter to support recursion, do not use it. 33 | 34 | .Example 35 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {"${key}: $($Dictionary[$key])" | Write-Host} 36 | 37 | Print each lowest level key name and value. 38 | 39 | .Example 40 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key] = Get-Random} 41 | 42 | Set each lowest level key to random value. 43 | 44 | .Example 45 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary.Remove($key)} 46 | 47 | Remove every lowest level key. 48 | 49 | .Example 50 | $Dictionary = @{Alfa = @{Bravo = 'Foo'}} | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key] = Get-Random} -PassThru 51 | 52 | Set each lowest level key to random value. 53 | -PassThru switch allows to capture resulting dictionary, which otherwise would be unavailable. 54 | 55 | .Example 56 | 57 | #Search dictionary: 58 | 59 | $Dictionary = @{ 60 | Alfa = @{ 61 | Bravo = @{ 62 | Charlie = 'FooBar' 63 | Delta = 'BarFoo' 64 | Echo = 'FarBoo' 65 | } 66 | } 67 | } 68 | 69 | # Search for lowest level keys with name 'Charlie' and output their values 70 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key]} -Include 'Charlie' 71 | 72 | # Search for all lowest level keys except 'Charlie' and output their values 73 | Step-Dictionary -Dictionary $Dictionary -ScriptBlock {$Dictionary[$key]} -Exclude 'Charlie' 74 | #> 75 | function Step-Dictionary 76 | { 77 | [CmdletBinding()] 78 | Param 79 | ( 80 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 81 | [ValidateNotNull()] 82 | [System.Collections.IDictionary]$Dictionary, 83 | 84 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 85 | [ValidateNotNullOrEmpty()] 86 | [scriptblock[]]$ScriptBlock, 87 | 88 | [Parameter(ValueFromPipelineByPropertyName = $true)] 89 | [ValidateNotNullOrEmpty()] 90 | [string[]]$Exclude, 91 | 92 | [Parameter(ValueFromPipelineByPropertyName = $true)] 93 | [ValidateNotNullOrEmpty()] 94 | [string[]]$Include = '*', 95 | 96 | [Parameter(ValueFromPipelineByPropertyName = $true)] 97 | [ValidateNotNullOrEmpty()] 98 | [int]$Depth = [Int32]::MaxValue, 99 | 100 | [Parameter(ValueFromPipelineByPropertyName = $true)] 101 | [switch]$PassThru, 102 | 103 | # Parameter below is to support recursion, do not use it 104 | [ValidateNotNullOrEmpty()] 105 | [int]$CurrentDepth = 0 106 | ) 107 | 108 | Process 109 | { 110 | Write-Verbose "Current depth: $CurrentDepth" 111 | 112 | foreach($key in @($Dictionary.Keys)) 113 | { 114 | Write-Verbose "Dictionary key: $key" 115 | 116 | if($null -ne $Dictionary[$key] -and $Dictionary[$key].GetType().GetInterfaces().Name -contains 'IDictionary') 117 | { 118 | Write-Verbose "The '$key' contains dictionary" 119 | 120 | if(($CurrentDepth + 1) -ge $Depth) 121 | { 122 | Write-Verbose "Skipping, reached maximum depth: $Depth" 123 | continue 124 | } 125 | 126 | Write-Verbose "Recursively calling '$($PSCmdlet.MyInvocation.MyCommand.Name)'" 127 | 128 | $PSBoundParameters.Dictionary = $Dictionary[$key] 129 | $PSBoundParameters.CurrentDepth = $CurrentDepth + 1 130 | & $PSCmdlet.MyInvocation.MyCommand.Name @PSBoundParameters 131 | } 132 | else 133 | { 134 | if 135 | ( 136 | # Include\Exclude filter with wildcard support 137 | $($key | 138 | Where-Object {$tmp = $_ ; ($Include | Where-Object {$tmp -like $_})} | 139 | Where-Object {$tmp = $_ ; !($Exclude | Where-Object {$tmp -like $_})}) 140 | ) 141 | { 142 | Write-Debug "Original '$key' value: $($Dictionary[$key])" 143 | 144 | # Execute scriptblocks 145 | foreach($sb in $ScriptBlock){ 146 | . $sb 147 | } 148 | 149 | Write-Debug "New '$key' value: $($Dictionary[$key])" 150 | } 151 | else 152 | { 153 | Write-Verbose "Skipping key: $key" 154 | } 155 | } 156 | } 157 | 158 | if ($PassThru -and 0 -eq $CurrentDepth) 159 | { 160 | Write-Verbose 'Writing dictionary to pipeline' 161 | $Dictionary 162 | } 163 | } 164 | } -------------------------------------------------------------------------------- /Use-Object.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | PowerShell-style version of C# 'using' statement. 4 | This function will take care of disposing .NET and releasing COM objects for you. 5 | 6 | .Description 7 | PowerShell-style version of C# 'using' statement. 8 | 9 | I felt that C# syntax is no quite fit for PowerShell, so I've made a 'pipelined' version. 10 | 11 | The object is passed via the pipeline and scriptblock is passed as parameter. 12 | The object is available to the scriptblock via $_ variable, similarly to 'ForEach-Obect'. 13 | 14 | More details here: https://beatcracker.wordpress.com/2017/12/09/yet-another-using-statement/ 15 | 16 | .Parameter ScriptBlock 17 | Scriptblock to execute, use '$_' to access object. 18 | 19 | .Example 20 | New-Object -TypeName System.IO.StreamWriter -ArgumentList 'c:\foo.txt' | Use-Object {$_.WriteLine('BAR')} 21 | 22 | Use StreamWriter to write text to file. Stream will be disposed and closed after scriptblock is executed. 23 | 24 | .Example 25 | New-Object -ComObject InternetExplorer.Application | Use-Object { 26 | $_.Visible = $true 27 | $_.navigate('https://bing.com') 28 | Start-Sleep -Seconds 10 29 | $_.Quit() 30 | } 31 | 32 | Use Internet Explorer to show website and release IE COM object afterwards. 33 | #> 34 | filter Use-Object { 35 | Param ( 36 | [ValidateNotNullOrEmpty()] 37 | [scriptblock]$ScriptBlock 38 | ) 39 | 40 | try { 41 | . $ScriptBlock 42 | } 43 | finally { 44 | if ($_ -is [System.IDisposable]) { 45 | 'Disposing: {0}' -f $_.GetType().Name | Write-Verbose 46 | if ($null -eq $DisposableObject.psbase) { 47 | $_.Dispose() 48 | } 49 | else { 50 | $_.psbase.Dispose() 51 | } 52 | } 53 | elseif ($_ -is [System.__ComObject]) { 54 | Write-Verbose 'Releasing COM object' 55 | if ([System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($_)) { 56 | Write-Error 'Failed to release COM object!' 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Use-ServiceAccount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | 4 | Wrapper around Win32 API functions for managing (Group) Managed Service Accounts 5 | 6 | .Description 7 | 8 | Wrapper around Win32 API functions for managing (Group) Managed Service Accounts. 9 | Allows to test/add/remove (G)MSAs without 'Active Directory' module. 10 | 11 | .Parameter Add 12 | 13 | Installs an existing Active Directory managed service account on the computer on which the cmdlet is run. 14 | 15 | .Parameter AccountPassword 16 | 17 | Specifies the account password as a secure string. 18 | This parameter enables you to specify the password of a standalone managed service account that you have provisioned and is ignored for group managed service accounts. 19 | This is required when you are installing a standalone managed service account on a server located on a segmented network (site) with read-only domain controllers (for example, a perimeter network or DMZ). 20 | In this case you should create the standalone managed service account, link it with the appropriate computer account, and assign a well-known password that must be passed when installing the standalone managed service account on the server on the read-only domain controller site with no access to writable domain controllers. 21 | 22 | .Parameter PromptForPassword 23 | 24 | Indicates that you can enter the password of a standalone managed service account that you have pre-provisioned and ignored for group managed service accounts. 25 | This is required when you are installing a standalone managed service account on a server located on a segmented network (site) with no access to writable domain controllers, but only read-only domain controllers (RODCs) (e.g. perimeter network or DMZ). 26 | In this case you should create the standalone managed service account, link it with the appropriate computer account, and assign a well-known password that must be passed when installing the standalone managed service account on the server on the RODC-only site. 27 | 28 | .Parameter Test 29 | 30 | Tests whether the specified standalone managed service account (sMSA) or group managed service account (gMSA) exists in the Netlogon store on the specified server. 31 | 32 | .Parameter Query 33 | 34 | Queries the specified service account from the local computer. 35 | The result indicates whether the account is ready for use, which means it can be authenticated and that it can access the domain using its current credentials. 36 | 37 | .Parameter Detailed 38 | 39 | Return MSA_INFO_STATE enumeration containing detailed information on (G)MSA state. 40 | See: https://msdn.microsoft.com/en-us/library/windows/desktop/dd894396.aspx 41 | 42 | .Parameter Remove 43 | 44 | Removes an Active Directory standalone managed service account (MSA) on the computer on which the cmdlet is run. 45 | For group MSAs, the cmdlet removes the group MSA from the cache. 46 | However, if a service is still using the group MSA and the host has permission to retrieve the password, then a new cache entry is created. 47 | The specified MSA must be installed on the computer. 48 | 49 | .Parameter ForceRemoveLocal 50 | 51 | Indicates that you can remove the account from the local security authority (LSA) if there is no access to a writable domain controller. 52 | This is required if you are uninstalling the MSA from a server that is placed in a segmented network such as a perimeter network with access only to a read-only domain controller. 53 | If you specify this parameter and the server has access to a writable domain controller, the account is also un-linked from the computer account in the directory. 54 | 55 | 56 | .Parameter AccountName 57 | 58 | Specifies the Active Directory MSA to uninstall. 59 | You can identify an MSA by its Security Account Manager (SAM) account name. 60 | 61 | .Example 62 | 63 | 'GMSA_Acount' | Use-ServiceAccount -Add 64 | 65 | Install Group Managed Service Account with SAM account name 'GMSA_Account' on the computer on which the cmdlet is run 66 | 67 | .Example 68 | 69 | Use-ServiceAccount -AccountName 'GMSA_Acount' -Add 70 | 71 | Install Group Managed Service Account with SAM account name 'GMSA_Account' on the computer on which the cmdlet is run 72 | 73 | .Example 74 | 75 | 'GMSA_Acount' | Use-ServiceAccount -Test 76 | 77 | Test whether the specified standalone managed service account (sMSA) or group managed service account (gMSA) exists in the Netlogon store on the this server. 78 | 79 | .Example 80 | 81 | 'GMSA_Acount' | Use-ServiceAccount -Query 82 | 83 | Queries the specified service account from the local computer. 84 | 85 | #> 86 | 87 | function Use-ServiceAccount 88 | { 89 | [CmdletBinding(DefaultParameterSetName = 'Add')] 90 | Param 91 | ( 92 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Add')] 93 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'AccountPassword')] 94 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'PromptForPassword')] 95 | [switch]$Add, 96 | 97 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'AccountPassword')] 98 | [string]$AccountPassword, 99 | 100 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'PromptForPassword')] 101 | [switch]$PromptForPassword, 102 | 103 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Test')] 104 | [switch]$Test, 105 | 106 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Query')] 107 | [switch]$Query, 108 | 109 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Query')] 110 | [switch]$Detailed, 111 | 112 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Remove')] 113 | [switch]$Remove, 114 | 115 | [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Remove')] 116 | [switch]$ForceRemoveLocal, 117 | 118 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 119 | [ValidateLength(1, 15)] 120 | [string[]]$AccountName 121 | ) 122 | 123 | Begin 124 | { 125 | 126 | $DllImport = @' 127 | 128 | // Service accounts 129 | 130 | [DllImport("logoncli.dll", CharSet = CharSet.Auto)] 131 | public static extern uint NetQueryServiceAccount( 132 | [In] string ServerName, 133 | [In] string AccountName, 134 | [In] uint InfoLevel, 135 | out IntPtr Buffer 136 | ); 137 | 138 | [DllImport("logoncli.dll", CharSet = CharSet.Auto)] 139 | public static extern uint NetIsServiceAccount( 140 | string ServerName, 141 | string AccountName, 142 | ref bool IsService 143 | ); 144 | 145 | [DllImport("logoncli.dll", CharSet = CharSet.Auto)] 146 | public static extern uint NetAddServiceAccount( 147 | string ServerName, 148 | string AccountName, 149 | string Reserved, 150 | int Flags 151 | ); 152 | 153 | [DllImport("logoncli.dll", CharSet = CharSet.Auto)] 154 | public static extern uint NetRemoveServiceAccount( 155 | string ServerName, 156 | string AccountName, 157 | int Flags 158 | ); 159 | 160 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 161 | public struct MSA_INFO 162 | { 163 | public MSA_INFO_STATE State; 164 | } 165 | 166 | [Flags] 167 | public enum MSA_INFO_STATE : uint 168 | { 169 | MsaInfoNotExist = 1u, 170 | MsaInfoNotService = 2u, 171 | MsaInfoCannotInstall = 3u, 172 | MsaInfoCanInstall = 4u, 173 | MsaInfoInstalled = 5u 174 | } 175 | 176 | 177 | // FormatMessage 178 | // https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs 179 | 180 | private const uint FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100; 181 | private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200; 182 | private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; 183 | private const uint LOAD_LIBRARY_AS_DATAFILE = 0x00000002; 184 | private const uint FORMAT_MESSAGE_FROM_HMODULE = 0x00000800; 185 | 186 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 187 | private static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, 188 | uint dwMessageId, uint dwLanguageId, 189 | [MarshalAs(UnmanagedType.LPWStr)] 190 | StringBuilder lpBuffer, 191 | uint nSize, IntPtr Arguments); 192 | 193 | [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] 194 | private static extern IntPtr LoadLibraryEx( 195 | [MarshalAs(UnmanagedType.LPWStr)] string lpFileName, 196 | IntPtr hFile, 197 | uint dwFlags 198 | ); 199 | 200 | [DllImport("kernel32.dll")] 201 | private static extern bool FreeLibrary(IntPtr hModule); 202 | 203 | public static uint FormatMessageFromModule(uint lastError, string moduleName, out String msg) 204 | { 205 | uint formatError = 0; 206 | msg = String.Empty; 207 | IntPtr moduleHandle = IntPtr.Zero; 208 | 209 | moduleHandle = LoadLibraryEx(moduleName, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE); 210 | if (moduleHandle == IntPtr.Zero) 211 | { 212 | return (uint)Marshal.GetLastWin32Error(); 213 | } 214 | 215 | try 216 | { 217 | uint dwFormatFlags = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE; 218 | uint LANGID = (uint)System.Globalization.CultureInfo.CurrentUICulture.LCID; 219 | 220 | StringBuilder outStringBuilder = new StringBuilder(1024); 221 | uint nChars = FormatMessage(dwFormatFlags, 222 | moduleHandle, 223 | lastError, 224 | LANGID, 225 | outStringBuilder, 226 | (uint)outStringBuilder.Capacity, 227 | IntPtr.Zero); 228 | 229 | if (nChars == 0) 230 | { 231 | formatError = (uint)Marshal.GetLastWin32Error(); 232 | } 233 | else 234 | { 235 | msg = outStringBuilder.ToString(); 236 | if (msg.EndsWith(Environment.NewLine, StringComparison.Ordinal)) 237 | { 238 | msg = msg.Substring(0, msg.Length - 2); 239 | } 240 | } 241 | } 242 | finally 243 | { 244 | FreeLibrary(moduleHandle); 245 | } 246 | return formatError; 247 | } 248 | '@ 249 | Add-Type -MemberDefinition $DllImport -Name ServiceAccount -Namespace LogonCli -ErrorAction Stop -UsingNamespace 'System.Text' 250 | 251 | function Format-MessageFromModule 252 | { 253 | [CmdletBinding()] 254 | Param 255 | ( 256 | [Parameter(Mandatory = $true, ValueFromPipeline = $true)] 257 | [uint32[]]$LastError, 258 | 259 | [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)] 260 | [string]$ModuleName 261 | ) 262 | 263 | Process 264 | { 265 | foreach ($err in $LastError) { 266 | $result = $null 267 | if (! ($ret = [LogonCli.ServiceAccount]::FormatMessageFromModule($err, $ModuleName, [ref]$result))) { 268 | $result 269 | } else { 270 | Write-Error "Can't get message ID: $err. Error code: $ret" 271 | } 272 | 273 | } 274 | } 275 | } 276 | } 277 | 278 | 279 | Process 280 | { 281 | foreach ($name in $AccountName) { 282 | Write-Verbose "Using account: $name" 283 | 284 | $result = 0 285 | 286 | if ($Test) { 287 | Write-Verbose 'Testing account using NetIsServiceAccount' 288 | if (!($ret = [LogonCli.ServiceAccount]::NetIsServiceAccount($null, $name, [ref]$result))) { 289 | $result 290 | } 291 | } elseif ($Query) { 292 | Write-Verbose 'Querying account detail using NetQueryServiceAccount' 293 | if (!($ret = [LogonCli.ServiceAccount]::NetQueryServiceAccount($null, $name, 0, [ref]$result))) { 294 | $result = [System.Runtime.InteropServices.Marshal]::PtrToStructure($result, [System.Type][LogonCli.ServiceAccount+MSA_INFO]) 295 | 296 | if ($Detailed) { 297 | Write-Verbose 'Returning detailed result' 298 | $result.State 299 | } else { 300 | if ($result.State -eq [LogonCli.ServiceAccount+MSA_INFO_STATE]::MsaInfoInstalled) { 301 | $true 302 | } else { 303 | switch ($result.State) { 304 | ([LogonCli.ServiceAccount+MSA_INFO_STATE]::MsaInfoNotExist) { 305 | Write-Warning "Cannot find Managed Service Account $name in the directory. Verify the Managed Service Account identity and call the cmdlet again." 306 | } 307 | 308 | ([LogonCli.ServiceAccount+MSA_INFO_STATE]::MsaInfoNotService) { 309 | Write-Warning "The $name is not a Managed Service Account. Verify the identity and call the cmdlet again." 310 | } 311 | 312 | ([LogonCli.ServiceAccount+MSA_INFO_STATE]::MsaInfoCannotInstall) { 313 | Write-Warning "Test failed for Managed Service Account $name. If standalone Managed Service Account, the account is linked to another computer object in the Active Directory. If group Managed Service Account, either this computer does not have permission to use the group MSA or this computer does not support all the Kerberos encryption types required for the gMSA. See the MSA operational log for more information." 314 | } 315 | 316 | ([LogonCli.ServiceAccount+MSA_INFO_STATE]::MsaInfoCanInstall) { 317 | Write-Warning "The Managed Service Account $name is not linked with any computer object in the directory." 318 | } 319 | } 320 | 321 | $false 322 | } 323 | } 324 | } 325 | } elseif ($Add) { 326 | Write-Verbose 'Installing account using NetAddServiceAccount' 327 | if ($PromptForPassword -or $AccountPassword) { 328 | if ($PromptForPassword) { 329 | $AccountPassword = Read-Host 330 | } 331 | 332 | $ret = [LogonCli.ServiceAccount]::NetAddServiceAccount( $null, $name, $AccountPassword, 2) 333 | } else { 334 | $ret = [LogonCli.ServiceAccount]::NetAddServiceAccount($null, $name, $null, 1) 335 | } 336 | } elseif ($Remove) { 337 | Write-Verbose 'Uninstalling account using NetRemoveServiceAccount' 338 | $ret = [LogonCli.ServiceAccount]::NetRemoveServiceAccount($null, $AccountName, 1) 339 | 340 | # fom winnt.h 341 | # define STATUS_NO_SUCH_DOMAIN 0xC00000DF 342 | if ((0xC00000DF -eq $ret) -and ($ForceRemoveLocal)) { 343 | Write-Verbose 'Can''t contact domain, removing local account data' 344 | $ret = [LogonCli.ServiceAccount]::NetRemoveServiceAccount($null, $AccountName, 2) 345 | } 346 | } 347 | 348 | if ($ret) { 349 | Write-Verbose "Returning user-friendly error message for status code: $ret" 350 | $ret | Format-MessageFromModule -ModuleName 'Ntdll.dll' | Write-Error 351 | } 352 | } 353 | } 354 | } -------------------------------------------------------------------------------- /Write-Host.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Write-Host but with ANSI colors! 4 | 5 | .Description 6 | Drop-in Write-Host replacement that uses ANSI escape codes to render colors. 7 | Allows for colorized output in CI systems. 8 | 9 | .Parameter Object 10 | Objects to display in the host. 11 | 12 | .Parameter ForegroundColor 13 | Specifies the text color. There is no default. 14 | 15 | .Parameter BackgroundColor 16 | Specifies the background color. There is no default. 17 | 18 | .Parameter Separator 19 | Specifies a separator string to insert between objects displayed by the host. 20 | 21 | .Parameter NoNewline 22 | The string representations of the input objects are concatenated to form the output. 23 | No spaces or newlines are inserted between the output strings. 24 | No newline is added after the last output string. 25 | 26 | .Example 27 | Write-Host 'Double rainbow!' -ForegroundColor Magenta -BackgroundColor Yellow 28 | 29 | .Notes 30 | Author : beatcracker (https://github.com/beatcracker) 31 | License: MS-PL (https://opensource.org/licenses/MS-PL) 32 | Source : https://github.com/beatcracker/Powershell-Misc 33 | #> 34 | function Write-Host { 35 | [CmdletBinding()] 36 | Param( 37 | [Parameter(ValueFromPipeline)] 38 | [Alias('Msg', 'Message')] 39 | [System.Object[]]$Object, 40 | [System.Object]$Separator, 41 | [System.ConsoleColor]$ForegroundColor, 42 | [System.ConsoleColor]$BackgroundColor, 43 | [switch]$NoNewline 44 | ) 45 | 46 | Begin { 47 | # Map ConsoleColor enum values to ANSI colors 48 | # https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit 49 | $AnsiColor = @( 50 | 30, 34, 32, 36, 31, 35, 33, 37, 90, 94, 92, 96, 91, 95, 93, 97 51 | ) 52 | # PS < 6.0 doesn't have `e escape character 53 | $Esc = [char]27 54 | $AnsiTemplate = "$Esc[{0}m{1}$Esc[{2}m" 55 | } 56 | 57 | Process { 58 | # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_special_characters#escape-e 59 | # https://docs.microsoft.com/en-us/powershell/scripting/windows-powershell/wmf/whats-new/console-improvements#vt100-support 60 | if ($Host.UI.SupportsVirtualTerminal) { 61 | $Method = if ($NoNewline) { 'Write' } else { 'WriteLine' } 62 | $Output = if ($Separator) { $Object -join $Separator } else { "$Object" } 63 | 64 | # Splitting by regex ensures that this will work on files from Windows/Linux/macOS 65 | # Get-Content .\Foobar.txt -Raw | Write-Host -ForegroundColor Red 66 | foreach ($item in $Output -split '\r\n|\r|\n') { 67 | if ("$BackgroundColor") { 68 | $item = $AnsiTemplate -f ($AnsiColor[$BackgroundColor.value__] + 10), $item, 49 69 | } 70 | if ("$ForegroundColor") { 71 | $item = $AnsiTemplate -f $AnsiColor[$ForegroundColor.value__], $item, 39 72 | } 73 | 74 | [System.Console]::$Method($item) 75 | } 76 | } 77 | else { 78 | Microsoft.PowerShell.Utility\Write-Host @PSBoundParameters 79 | } 80 | } 81 | } --------------------------------------------------------------------------------