PSUtil.psm1
$script:ModuleRoot = $PSScriptRoot $script:ModuleVersion = (Import-PowerShellDataFile -Path "$($script:ModuleRoot)\PSUtil.psd1").ModuleVersion # Detect whether at some level dotsourcing was enforced $script:doDotSource = Get-PSFConfigValue -FullName PSUtil.Import.DoDotSource -Fallback $false if ($PSUtil_dotsourcemodule) { $script:doDotSource = $true } <# Note on Resolve-Path: All paths are sent through Resolve-Path/Resolve-PSFPath in order to convert them to the correct path separator. This allows ignoring path separators throughout the import sequence, which could otherwise cause trouble depending on OS. Resolve-Path can only be used for paths that already exist, Resolve-PSFPath can accept that the last leaf my not exist. This is important when testing for paths. #> # Detect whether at some level loading individual module files, rather than the compiled module was enforced $importIndividualFiles = Get-PSFConfigValue -FullName PSUtil.Import.IndividualFiles -Fallback $false if ($PSUtil_importIndividualFiles) { $importIndividualFiles = $true } if (Test-Path (Resolve-PSFPath -Path "$($script:ModuleRoot)\..\.git" -SingleItem -NewChild)) { $importIndividualFiles = $true } if ("<was compiled>" -eq '<was not compiled>') { $importIndividualFiles = $true } function Import-ModuleFile { <# .SYNOPSIS Loads files into the module on module import. .DESCRIPTION This helper function is used during module initialization. It should always be dotsourced itself, in order to proper function. This provides a central location to react to files being imported, if later desired .PARAMETER Path The path to the file to load .EXAMPLE PS C:\> . Import-ModuleFile -File $function.FullName Imports the file stored in $function according to import policy #> [CmdletBinding()] Param ( [string] $Path ) if ($doDotSource) { . (Resolve-Path $Path) } else { $ExecutionContext.InvokeCommand.InvokeScript($false, ([scriptblock]::Create([io.file]::ReadAllText((Resolve-Path $Path)))), $null, $null) } } #region Load individual files if ($importIndividualFiles) { # Execute Preimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\preimport.ps1" # Import all internal functions foreach ($function in (Get-ChildItem "$ModuleRoot\internal\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Import all public functions foreach ($function in (Get-ChildItem "$ModuleRoot\functions" -Filter "*.ps1" -Recurse -ErrorAction Ignore)) { . Import-ModuleFile -Path $function.FullName } # Execute Postimport actions . Import-ModuleFile -Path "$ModuleRoot\internal\scripts\postimport.ps1" # End it here, do not load compiled code below return } #endregion Load individual files #region Load compiled code Register-PSFConfigValidation -Name "PSUBrowseHistoryOptions" -ScriptBlock { param ( $Value ) $result = New-Object PSObject -Property @{ Message = "" Value = $null Success = $false } try { [PSUtil.Configuration.HistoryOption]$option = $Value } catch { $result.Message = "Could not convert $Value into a valid help option. Please specify either of these: Session | Global" return $result } $result.Success = $true $result.Value = $option return $result } Register-PSFConfigValidation -Name "PSUConsoleWidth" -ScriptBlock { param ( $Value ) $result = New-Object PSObject -Property @{ Message = "" Value = $null Success = $false } try { [int]$option = $Value } catch { $result.Message = "Could not convert $Value into a valid integer | $_" return $result } if ($option -le 0) { $result.Message = "Cannot specify a window width of 0 or less" return $result } if ($option -gt $Host.UI.RawUI.MaxWindowSize.Width) { $result.Message = "Cannot specify a window width larger than the maximum width: $option / $($Host.UI.RawUI.MaxWindowSize.Width)" return $result } $result.Success = $true $result.Value = $option return $result } Register-PSFConfigValidation -Name "PSUGetHelpOptions" -ScriptBlock { param ( $Value ) $result = New-Object PSObject -Property @{ Message = "" Value = $null Success = $false } try { [PSUtil.Configuration.HelpOption]$option = $Value } catch { $result.Message = "Could not convert $Value into a valid help option. Please specify either of these: Short | Detailed | Examples | Full | Window | Online" return $result } $result.Success = $true $result.Value = $option return $result } # Configure the current window width Set-PSFConfig -Module PSUtil -Name 'Console.Width' -Value ($Host.UI.RawUI.WindowSize.Width) -Initialize -Validation "PSUConsoleWidth" -Handler { Set-PSUShell -WindowWidth $args[0] } -Description "The width of the current console" # Buffer Length Set-PSFConfig -Module PSUtil -Name 'Console.Buffer' -Value ($Host.UI.RawUI.BufferSize.Height) -Initialize -Validation "integerpositive" -Handler { Set-PSUShell -BufferLength $args[0] } -Description "The length of the console screen history" # Foreground Color Set-PSFConfig -Module PSUtil -Name 'Console.ForegroundColor' -Value ($Host.ui.rawui.ForegroundColor) -Initialize -Validation "consolecolor" -Handler { Set-PSUShell -ForegroundColor $args[0] } -Description "The foreground color used in the PowerShell console" # Background Color Set-PSFConfig -Module PSUtil -Name 'Console.BackgroundColor' -Value ($Host.ui.rawui.BackgroundColor) -Initialize -Validation "consolecolor" -Handler { Set-PSUShell -BackgroundColor $args[0] } -Description "The background color used in the PowerShell console" # Window Title Set-PSFConfig -Module PSUtil -Name 'Console.WindowTitle' -Value ($Host.ui.rawui.Windowtitle) -Initialize -Validation "string" -Handler { Set-PSUShell -WindowTitle $args[0] } -Description "The background color used in the PowerShell console" Set-PSFConfig -Module PSUtil -Name 'Import.Aliases.Grep' -Value $true -Initialize -Validation "bool" -Handler { } -Description "Whether the module will on import create an alias named 'grep' for Select-String" Set-PSFConfig -Module PSUtil -Name 'Import.Keybindings' -Value $true -Initialize -Validation "bool" -Handler { } -Description "Whether the module will on import register keybindings in PSReadline" Set-PSFConfig -Module PSUtil -Name 'Import.Alias.SystemOverride' -Value $false -Initialize -Validation "bool" -Description "Whether the module will on import pverwrite some default aliases with custom versions. Affects 'select' and 'gm'" Set-PSFConfig -Module 'PSUtil' -Name 'Import.DoDotSource' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be dotsourced on import. By default, the files of this module are read as string value and invoked, which is faster but worse on debugging." Set-PSFConfig -Module 'PSUtil' -Name 'Import.IndividualFiles' -Value $false -Initialize -Validation 'bool' -Description "Whether the module files should be imported individually. During the module build, all module code is compiled into few files, which are imported instead by default. Loading the compiled versions is faster, using the individual files is easier for debugging and testing out adjustments." Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.GetHelp' -Value "F1" -Initialize -Description "The key chord(s) the open help functionality is bound to. Will provide help for the currently typed command." Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.ExpandAlias' -Value "Shift+Spacebar" -Initialize -Description "The key chord(s) the alias expansion functionality is bound to. Will expand all aliases in the current input." Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.CopyAll' -Value "Ctrl+Shift+c" -Initialize -Description "The key chord(s) the copy all functionality is bound to. Will copy all lines of the current input to the clipboard." Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.BrowseHistory' -Value "F7" -Initialize -Description "The key chord(s) the browse history functionality is bound to. Will open a gridview and allow you to pick from your input history, then insert the chosen line(s) as your current input." Set-PSFConfig -Module 'PSUtil' -Name 'Keybinding.SendToHistory' -Value "Alt+w" -Initialize -Description "The key chord(s) the send to history functionality is bound to. Your current input will be sent to history without actually executing it. Access it by using the Arrow-UP key." Set-PSFConfig -Module PSUtil -Name 'Help.Preference' -Value ([PSUtil.Configuration.HelpOption]::Window) -Initialize -Validation "PSUGetHelpOptions" -Handler { } -Description "The way in which help is shown when pressing the 'F1' key. Can be any of the following options: Short | Detailed | Examples | Full | Window | Online" Set-PSFConfig -Module PSUtil -Name 'History.Preference' -Value ([PSUtil.Configuration.HistoryOption]::Session) -Initialize -Validation "PSUBrowseHistoryOptions" -Handler { } -Description "Where the system will retrieve input history when pressing the 'F7' key. Can be any of the following options: Session | Global. Session includes history since the process was started, Global will try to look up the history from PSReadline log-file instead" Set-PSFConfig -Module PSUtil -Name 'History.Limit' -Value (-1) -Initialize -Validation 'integer' -Handler { } -Description "The maximum number of history entries to show when pressing the 'F7' key. Negative numbers disable limit" Set-PSFConfig -Module PSUtil -Name 'Expand.DefaultProperties' -Value "Definition", "Guid", "DisinguishedName", "FullName", "Name", "Length" -Initialize -Validation stringarray -Description "The properties Expand-PSUObject (exp) picks from by default" Set-PSFConfig -Module PSUtil -Name 'Path.Temp' -Value $([System.IO.Path]::GetTempPath()) -Initialize -Validation "string" -Handler { } -Description "The path to move to when calling Invoke-PSUTemp (temp)" Set-PSFConfig -Module PSUtil -Name 'Path.BackupStepsDefault' -Value 1 -Initialize -Validation "integer" -Description "The number of levels you stup up when calling Backup-PSULocation (bu) without parameter" function Import-PSUAlias { <# .SYNOPSIS Internal command to set aliases in a user-controlled fashion. .DESCRIPTION Internal command to set aliases in a user-controlled fashion. - Can be blocked by setting a config "PSUtil.Import.Aliases.$Name" to $false. - Will not overwrite existing aliases .PARAMETER Name Name of the alias to set. .PARAMETER Command Name of the command to alias. .EXAMPLE PS C:\> Import-PSUAlias -Name grep -Command Select-String Sets the alias grep for the command Select-String #> [CmdletBinding()] Param ( $Name, $Command ) if (((-not (Test-Path alias:$name)) -or ($Name -eq "Select") -or ($Name -eq "gm")) -and (Get-PSFConfigValue -FullName PSUtil.Import.Aliases.$name -Fallback $true)) { New-Alias -Name $Name -Value $Command -Force -Scope Global } } function Backup-PSULocation { <# .SYNOPSIS Sets the location n number of levels up. .DESCRIPTION You no longer have to cd ..\..\..\..\ to move back four levels. You can now just type bu 4 .PARAMETER Levels Number of levels to move back. .EXAMPLE PS C:\Users\dlbm3\source\pullrequests\somePR\vsteam> bu 4 PS C:\Users\dlbm3> .NOTES Author: Donovan Brown Source: http://donovanbrown.com/post/Why-cd-when-you-can-just-backup Thank you for sharing and granting permission to use this convenience :) #> [CmdletBinding()] param ( [int] $Levels = (Get-PSFConfigValue -FullName 'PSUtil.Path.BackupStepsDefault' -Fallback 1) ) Set-Location -Path (,".." * $Levels | Join-PSUString -With ([System.IO.Path]::DirectorySeparatorChar)) } Import-PSUAlias -Name "bu" -Command "Backup-PSULocation" function Invoke-PSUDesktop { <# .SYNOPSIS Function that sets the current console path to the user desktop. .DESCRIPTION Function that sets the current console path to the user desktop. Uses the current user's desktop by default, but can be set to the desktop of any locally available profile. .PARAMETER User Alias: u Choose which user's desktop path to move to. Must be available as a local profile for things to work out. .PARAMETER Get Alias: g Returns the path, rather than changing the location .EXAMPLE PS C:\> Desktop Sets the current location to the desktop path of the current user. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] [CmdletBinding()] Param ( [Parameter(Position = 0)] [Alias('u')] [string] $User = $env:USERNAME, [Alias('g')] [switch] $Get ) # Default Path for current user $Path = "$env:SystemDrive\Users\$User\Desktop" if (-not (Test-Path $Path)) { Stop-PSFFunction -Message "Path to Desktop not found: $Path" -Tag fail -Target $User -Category InvalidArgument return } if ($Get) { return $Path } else { Push-Location $Path } } Import-PSUAlias -Name "desktop" -Command "Invoke-PSUDesktop" function Invoke-PSUExplorer { <# .SYNOPSIS Opens the windows explorer at the specified position. .DESCRIPTION Opens the windows explorer at the specified position. .PARAMETER Path Alias: FullName Default: (Get-Location).Path The folder to open in explorer. If a file was passed the containing folder will be opened instead. .PARAMETER Module The module, the base folder of which should be opened. .PARAMETER Duplicates Setting this switch will cause the function to open the same folder multiple times, if it was passed multiple times. By default, the function will not open the same folder multiple times (a dir of a directory with multiple files would otherwise cause multiple open windows). .EXAMPLE PS C:\> dir | Explorer Opens each folder in the current directory in a separate explorer Window. #> [CmdletBinding()] Param ( [Parameter(Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('FullName')] [string[]] $Path = (Get-Location).ProviderPath, [System.Management.Automation.PSModuleInfo[]] $Module, [switch] $Duplicates ) Begin { Write-PSFMessage -Level Debug -Message "Opening Windows Explorer at target path(s)" -Tag start # Contains previously opened folders $List = @() } Process { foreach ($Item in $Path) { $resolvedPath = Resolve-Path $Item if (-not (Test-Path $resolvedPath)) { Stop-PSFFunction -Message "Path not found: $resolvedPath | $Item" -Continue -Category InvalidArgument -Tag input -Target $Item } $object = Get-Item $resolvedPath if ($object.PSIsContainer) { $finalPath = $object.FullName } else { $finalPath = $object.Directory.FullName } # If it was already opened once, skip it unless duplicates are enabled if ((-not $Duplicates) -and ($List -contains $finalPath)) { Write-PSFMessage -Level Verbose -Message "Skipping folder since it already was opened once: $finalPath" -Target $Item -Tag skip continue } explorer.exe $finalPath $List += $finalPath } foreach ($Item in $Module) { if ((-not $Duplicates) -and ($List -contains $Item.ModuleBase)) { Write-PSFMessage -Level Verbose -Message "Skipping folder since it already was opened once: $($Item.ModuleBase)" -Target $Item -Tag skip continue } explorer.exe $Item.ModuleBase $List += $Item.ModuleBase } } End { Write-PSFMessage -Level Debug -Message "Opening Windows Explorer at target path(s)" -Tag end } } Import-PSUAlias -Name "explorer" -Command "Invoke-PSUExplorer" function Invoke-PSUTemp { <# .SYNOPSIS Moves the current location to a temp directory. .DESCRIPTION Moves the current location to a temp directory. The path returned can be set by configuring the 'psutil.path.temp' configuration. E.g.: Set-PSFConfig "psutil.path.temp" "D:\temp\_Dump" If this configuration is not set, it will check the following locations and return the first one found: C:\Temp D:\Temp E:\Temp C:\Service $env:temp .PARAMETER Get Alias: g Rather than move to the directory, return its path. .EXAMPLE PS C:\> Invoke-PSUTemp Moves to the temporary directory. #> [CmdletBinding()] Param ( [Alias('g')] [switch] $Get ) if ($Get) { Get-PSFConfigValue -FullName 'PSUtil.Path.Temp' -Fallback $env:TEMP } else { Push-Location -Path (Get-PSFConfigValue -FullName 'PSUtil.Path.Temp' -Fallback $env:TEMP) } } Import-PSUAlias -Name "temp" -Command "Invoke-PSUTemp" function New-PSUDirectory { <# .SYNOPSIS Creates a folder and moves the current path to it. .DESCRIPTION Creates a folder and moves the current path to it. .PARAMETER Path Name of the folder to create and move to. .EXAMPLE PS C:\> mcd Test creates folder C:\Test, then moves the current location to it. .NOTES Author: Donovan Brown Source: http://donovanbrown.com/post/How-to-create-and-navigate-a-directory-with-a-single-command Thank you for sharing and granting permission to use this convenience :) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Path ) New-Item -Path $Path -ItemType Directory Set-Location -Path $Path } Import-PSUAlias -Name "mcd" -Command "New-PSUDirectory" function Set-PSUDrive { <# .SYNOPSIS Creates a new psdrive, and moves location to it. .DESCRIPTION Will create a PSDrive, by default in the current path. This allows swiftly reducing path length. Then it will immediately change location to the new drive. .PARAMETER Name What to name the new PSDrive? .PARAMETER Root Default: . The root of the new drive. .EXAMPLE PS C:\> set-as pr Sets the current path as drive "pr" and sets it as the current location. .NOTES Author: Donovan Brown Source: http://donovanbrown.com/post/Shorten-your-PowerShell-directory-path Thank you for sharing and granting permission to use this convenience :) #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $Name, [string] $Root = "." ) $path = Resolve-Path $Root $null = New-PSDrive -PSProvider $path.Provider -Name $Name -Root $Root -Scope Global Set-Location -LiteralPath "$($Name):" } Import-PSUAlias -Name "set-as" -Command "Set-PSUDrive" function Convert-PSUObject { <# .SYNOPSIS Converts objects from one data-type/-format to another. .DESCRIPTION Converts objects from one data-type/-format to another. For example can this be used to convert numbers from binary to hex. This function can be dynamically extended by registering conversion paths. Use Register-PSUObjectConversion to set up such a type conversion. .PARAMETER InputObject The object(s) to convert. .PARAMETER From The type/format that is assumed to be the input type. .PARAMETER To The type/format that the input is attempted to convert to. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> 100..110 | convert IntDec IntHex Converts the numbers 100 through 110 from decimal to hexadecimal. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] $InputObject, [Parameter(Mandatory = $true, Position = 0)] [string] $From, [Parameter(Mandatory = $true, Position = 1)] [string] $To, [switch] $EnableException ) begin { Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug','start','param' if (-not ([PSUtil.Object.ObjectHost]::Conversions.ContainsKey("$($From):$($To)".ToLower()))) { Stop-PSFFunction -Message "No conversion path configured for $From --> $To" -EnableException $EnableException -Category NotImplemented -Tag 'fail', 'input', 'convert' return } $scriptBlock = [System.Management.Automation.ScriptBlock]::Create([PSUtil.Object.ObjectHost]::Conversions["$($From):$($To)".ToLower()].Script) } process { if (Test-PSFFunctionInterrupt) { return } foreach ($item in $InputObject) { try { $scriptBlock.Invoke($item) } catch { Stop-PSFFunction -Message "Failed to convert $item from $From to $To" -EnableException $EnableException -ErrorRecord $_ -Target $item -Tag 'fail','convert','item' -Continue } } } end { if (Test-PSFFunctionInterrupt) { return } } } Import-PSUAlias -Name convert -Command Convert-PSUObject function Expand-PSUObject { <# .SYNOPSIS A comfortable replacement for Select-Object -ExpandProperty. .DESCRIPTION A comfortable replacement for Select-Object -ExpandProperty. Allows extracting properties with less typing and more flexibility: Preferred Properties: By defining a list of property-names in $DefaultExpandedProperties the user can determine his own list of preferred properties to expand. This allows using this command without specifying a property at all. It will then check the first object for the property to use (starting from the first element of the list until it finds an exact case-insensitive match). Defined Property: The user can specify the exact property to extract. This is the same behavior as Select-Object -ExpandProperty, with less typing (dir | exp length). Like / Match comparison: Specifying either like or match allows extracting any number of matching properties from each object. Note that this is a somewhat more CPU-expensive operation (which shouldn't matter unless with gargantuan numbers of objects). .PARAMETER Name ParSet: Equals, Like, Match The name of the Property to expand. .PARAMETER Like ParSet: Like Expands all properties that match the -Name parameter using -like comparison. .PARAMETER Match ParSet: Match Expands all properties that match the -Name parameter using -match comparison. .PARAMETER InputObject The objects whose properties are to be expanded. .EXAMPLE PS C:\> dir | exp Expands the property whose name is the first on the defaults list ($DefaultExpandedProperties). By default, FullName would be expanded. .EXAMPLE PS C:\> dir | exp length Expands the length property of all objects returned by dir. Simply ignores those that do not have the property (folders). .EXAMPLE PS C:\> dir | exp name -match Expands all properties from all objects returned by dir that match the string "name" ("PSChildName", "FullName", "Name", "BaseName" for directories) #> [CmdletBinding(DefaultParameterSetName = "Equals")] Param ( [Parameter(Position = 0, ParameterSetName = "Equals")] [Parameter(Position = 0, ParameterSetName = "Like", Mandatory = $true)] [Parameter(Position = 0, ParameterSetName = "Match", Mandatory = $true)] [string] $Name, [Parameter(ParameterSetName = "Like", Mandatory = $true)] [switch] $Like, [Parameter(ParameterSetName = "Match", Mandatory = $true)] [switch] $Match, [Parameter(ValueFromPipeline = $true)] [object] $InputObject ) Begin { Write-PSFMessage -Level Debug -Message "Expanding Objects" -Tag start $ParSet = $PSCmdlet.ParameterSetName Write-PSFMessage -Level InternalComment -Message "Active Parameterset: $ParSet | Bound Parameters: $($PSBoundParameters.Keys -join ", ")" -Tag start # Null the local scoped variable (So later checks for existence don't return super-scoped variables) $n9ZPiBh8CI = $null [bool]$____found = $false # If a property was specified, set it and return it if (Test-PSFParameterBinding -ParameterName "Name") { $n9ZPiBh8CI = $Name $____found = $true } $DefaultExpandedProperties = Get-PSFConfigValue -FullName 'PSutil.Expand.DefaultProperties' } process { :main foreach ($Object in $InputObject) { if ($null -eq $Object) { continue } switch ($ParSet) { #region Equals "Equals" { # If we didn't ask for a property in specific, and we have something prepared for this type: Run it if ((Test-PSFParameterBinding -ParameterName "Name" -Not) -and ([PSUtil.Object.ObjectHost]::ExpandedTypes[$Object.GetType().FullName])) { [PSUtil.Object.ObjectHost]::ExpandedTypes[$Object.GetType()].Invoke($Object) continue main } # If we already have determined the property to use, return it if ($____found) { if ($null -ne $Object.$n9ZPiBh8CI) { $Object.$n9ZPiBh8CI } continue main } # Otherwise, search through defaults and try to match foreach ($Def in $DefaultExpandedProperties) { if (Get-Member -InputObject $Object -MemberType 'Properties' -Name $Def) { $n9ZPiBh8CI = $Def $____found = $true if ($null -ne $Object.$n9ZPiBh8CI) { $Object.$n9ZPiBh8CI } break } } continue main } #endregion Equals #region Like "Like" { # Return all properties whose name are similar foreach ($prop in ($Object.PSObject.Properties | Where-Object Name -like $Name | Select-Object -ExpandProperty Name)) { if ($null -ne $Object.$prop) { $Object.$prop } } continue } #endregion Like #region Match "Match" { # Return all properties whose name match foreach ($prop in ($Object.PSObject.Properties | Where-Object Name -Match $Name | Select-Object -ExpandProperty Name)) { if ($null -ne $Object.$prop) { $Object.$prop } } continue main } #endregion Match } } } End { Write-PSFMessage -Level Debug -Message "Expanding Objects" -Tag end } } Import-PSUAlias -Name "exp" -Command "Expand-PSUObject" function Register-PSUObjectConversion { <# .SYNOPSIS Registers an object conversion for Convert-PSUObject. .DESCRIPTION This command can be used to register an object conversion for Convert-PSUObject, allowing the user to extend the conversion utility as desired. .PARAMETER From The input type. Using a suitable shorthand is recommended ("int" rather than "System.Int32", etc.). .PARAMETER To The conversion target type. Using a suitable shorthand is recommended ("int" rather than "System.Int32", etc.). .PARAMETER ScriptBlock The scriptblock that will be invoked to convert. Receives a single argument: The input object to convert. .EXAMPLE PS C:\> Register-PSUObjectConversion -From 'dec' -To 'oct' -ScriptBlock $ScriptBlock Registers a conversion that is supposed to convert a decimal into an octal number. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true)] [string] $From, [Parameter(Mandatory = $true)] [string] $To, [Parameter(Mandatory = $true)] [System.Management.Automation.ScriptBlock] $ScriptBlock ) process { $conversion = New-Object PSUtil.Object.ObjectConversionMapping -Property @{ From = $From.ToLower() To = $To.ToLower() Script = $ScriptBlock } [PSUtil.Object.ObjectHost]::Conversions["$($From):$($To)".ToLower()] = $conversion } } function Select-PSUObjectSample { <# .SYNOPSIS Used to only pick a sample from the objects passed to the function. .DESCRIPTION Used to only pick a sample from the objects passed to the function. .PARAMETER InputObject The objects to pick a sample from. .PARAMETER Skip How many objects to skip. .PARAMETER Number How many objects to pick Use a negative number to pick the last X items instead. .EXAMPLE PS C:\> Get-ChildItem | Select-PSUObjectSample -Skip 1 -Number 3 Scans the current directory, skips the first returned object, then passes through the next three objects and skips the rest. .EXAMPLE PS C:\> dir | s 3 1 Same as the previous example, only this time using aliases and positional binding. Scans the current directory, skips the first returned object, then passes through the next three objects and skips the rest. #> [CmdletBinding()] Param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true)] $InputObject, [Parameter(Position = 1)] [int] $Skip = 0, [Parameter(Position = 0)] [int] $Number = 1 ) Begin { $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Select-Object', [System.Management.Automation.CommandTypes]::Cmdlet) $splat = @{ } if ($Skip -gt 0) { $splat["Skip"] = $Skip } if ($Number -ge 0) { $splat["First"] = $Number + 1 } else { $splat["Last"] = $Number * -1 } $scriptCmd = { & $wrappedCmd @splat } $steppablePipeline = $scriptCmd.GetSteppablePipeline() $steppablePipeline.Begin($true) } Process { foreach ($o in $InputObject) { $steppablePipeline.Process($o) } } End { $steppablePipeline.End() } } Import-PSUAlias -Name "s" -Command "Select-PSUObjectSample" function Set-PSUObjectType { <# .SYNOPSIS Tries to convert an object from one type of another. .DESCRIPTION Tries to convert an object from one type of another. .PARAMETER InputObject The objects to convert. .PARAMETER Type ParSet: Type The type to cast to. .PARAMETER TypeName ParSet: String The type to cast to. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> "01", "02", "03", "42" | Set-PSUObjectType "int" Tries to convert strings with numeric values into pure integers (hint: This will probably succeede). #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding(DefaultParameterSetName = "String")] Param ( [Parameter(ValueFromPipeline = $true)] $InputObject, [Parameter(ParameterSetName = "Type")] [Type] $Type, [Parameter(ParameterSetName = "String", Position = 0)] [String] $TypeName, [switch] $EnableException ) Begin { Write-PSFMessage -Level Debug -Message "Casting Objects to another type" -Tag start $ParSet = $PSCmdlet.ParameterSetName Write-PSFMessage -Level InternalComment -Message "Active Parameterset: $ParSet | Bound Parameters: $($PSBoundParameters.Keys -join ", ")" -Tag start switch ($ParSet) { "Type" { $name = $Type.FullName } "String" { $name = $TypeName } } } Process { foreach ($object in $InputObject) { $temp = $null $temp = $object -as $name if ($temp) { $temp } else { Stop-PSFFunction -Message "Failed to convert '$object' to '$name'" -EnableException $EnableException -Category InvalidData -Tag fail, cast -Target $object -Continue } } } End { Write-PSFMessage -Level Debug -Message "Casting Objects to another type" -Tag end } } Import-PSUAlias -Name "cast" -Command "Set-PSUObjectType" function Select-PSUFunctionCode { <# .SYNOPSIS Function that shows you the definition of a function and allows you to select lines to copy to your clipboard. .DESCRIPTION Function that shows you the definition of a function and allows you to select lines to copy to your clipboard. After running this command you will see a GridView pop up. Select as many lines of code as you would like and select ok to copy them to your clipboard. .PARAMETER Function A description of the Function parameter. .PARAMETER NoWait Shows function code in gridview and returns control without waiting for the window to close .PARAMETER PassThru Presents input command(s) in gridview, selected lines (if any) get returned as output .PARAMETER NoTrim If enabled, the white space will not be trimmed. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> Select-PSUFunctionCode -function 'Start-PSUTimer' This will open up the code for the function Start-PSUTimer in a GridView window. .EXAMPLE PS C:\> Get-Command timer | Select-PSUFunctionCode You can also pipe functions in. .NOTES Author: Andrew Pla #> [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string[]] $Function, [Alias('w')] [Parameter(ParameterSetName = 'NoWait')] [switch] $NoWait, [Alias('p')] [Parameter(ParameterSetName = 'PassThru')] [switch] $PassThru, [Alias('t')] [switch] $NoTrim, [switch] $EnableException ) begin { Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param' if ($PSVersionTable.PSVersion.Major -ge 6) { Stop-PSFFunction -Message "This command is not supported on PowerShell v6 or later!" -Category NotEnabled -Tag fail, ps6 return } $FinalArray = [System.Collections.Arraylist]@() } process { if (Test-PSFFunctionInterrupt) { return } #region Process Items :main foreach ($item in $Function) { #region Resolve Command try { $command = Get-Command $item -ErrorAction Stop } catch { Stop-PSFFunction -Message "Failed to resolve command $item" -Tag fail -ErrorRecord $_ -Target $item -Continue -ContinueLabel main -EnableException $EnableException } switch ($command.CommandType) { 'Alias' { if ($command.ResolvedCommand.CommandType -eq "Function") { $functionText = $command.ResolvedCommand | Expand-PSUObject | Split-PSUString "`n" } else { Stop-PSFFunction -Message "$($command.ResolvedCommand.CommandType) not supported: The alias $item resolves to $($command.ResolvedCommand). Please supply a function or an alias for a function." -Tag fail -Target $item -Continue -ContinueLabel main -EnableException $EnableException } } 'Function' { $functionText = $command | Expand-PSUObject | Split-PSUString "`n" } default { Stop-PSFFunction -Message "$($command.CommandType) not supported: $item. Please supply a function or an alias for a function." -Tag fail -Target $item -Continue -ContinueLabel main -EnableException $EnableException } } #endregion Resolve Command #region Process Definition content $count = 1 foreach ($line in $functionText) { $Object = [PSCustomObject]@{ LineNumber = $Count Text = $line Function = $item } $count++ if ($NoTrim) { $null = $FinalArray.add($Object) } else { if (-not ([string]::IsNullOrWhiteSpace($line))) { $null = $FinalArray.add($Object) } } } #endregion Process Definition content } #endregion Process Items } end { if (Test-PSFFunctionInterrupt) { return } # This is the default behavior with no params if (-not ($NoWait -or $PassThru)) { $data = $FinalArray | Out-GridView -PassThru -Title 'Select-PSFunctionCode' | Expand-PSUObject text if ($data) { $data | Set-Clipboard } } if ($NoWait) { $FinalArray | Out-GridView -Title 'Select-PSFunctionCode' } if ($PassThru) { $FinalArray | Out-GridView -PassThru -Title 'Select-PSFunctionCode' } } } Import-PSUAlias -Name "inspect" -Command "Select-PSUFunctionCode" function Set-PSUShell { <# .SYNOPSIS Command that sets various console properties .DESCRIPTION Command that sets various console properties. .PARAMETER WindowWidth The width of the console window. Not much of a change on windows 10, more of a chore on older console hosts. .PARAMETER BackgroundColor The background color to use. Is PSReadline aware. .PARAMETER ForegroundColor The foreground color to use. Is PSReadline aware. .PARAMETER BufferLength How lengthy a memory the console screen keeps. The size of the stuff cls clears. .PARAMETER WindowTitle The title the window should have. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> Set-PSUShell -WindowWidth 140 -WindowTitle "The Foo Shell" -ForegroundColor DarkGreen -BackgroundColor Black Sets the current shell to ... - 140 pixel width - have a title of "The Foo Shell" - Use a foreground color of DarkGreen for all output, default prompt color and comment color (PSReadline syntax detection remains unaffected) - Use a background color of Black #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] Param ( [int] $WindowWidth, [System.ConsoleColor] $BackgroundColor, [System.ConsoleColor] $ForegroundColor, [int] $BufferLength, [string] $WindowTitle, [switch] $EnableException ) # Test whether the PSReadline Module is loaded $PSReadline = $null -ne (Get-Module PSReadline) #region Utility Functions function Set-ShellWindowWidth { [CmdletBinding()] Param ( $WindowWidth ) $currentWindow = $host.ui.rawui.WindowSize $currentBuffer = $host.ui.rawui.Buffersize if ($currentBuffer.Width -gt $WindowWidth) { # Set Window $currentWindow.Width = $WindowWidth $host.ui.rawui.WindowSize = $currentWindow # Set Buffer $currentBuffer.Width = $WindowWidth $host.ui.rawui.Buffersize = $currentBuffer } else { # Set Buffer $currentBuffer.Width = $WindowWidth $host.ui.rawui.Buffersize = $currentBuffer # Set Window $currentWindow.Width = $WindowWidth $host.ui.rawui.WindowSize = $currentWindow } } #endregion Utility Functions #region Set Buffer if (Test-PSFParameterBinding -ParameterName "BufferLength") { $currentBuffer = $host.ui.rawui.Buffersize $currentBuffer.Height = $BufferLength $host.ui.rawui.Buffersize = $currentBuffer } #endregion Set Buffer #region Set Foreground Color if (Test-PSFParameterBinding -ParameterName "ForegroundColor") { $host.ui.rawui.ForegroundColor = $ForegroundColor if ($PSReadline) { Set-PSReadlineOption -ContinuationPromptForegroundColor $ForegroundColor Set-PSReadlineOption -ForegroundColor $ForegroundColor -TokenKind 'Comment' Set-PSReadlineOption -ForegroundColor $ForegroundColor -TokenKind None } } #endregion Set Foreground Color #region Set Background Color if (Test-PSFParameterBinding -ParameterName "BackgroundColor") { $host.ui.rawui.BackgroundColor = $BackgroundColor if ($PSReadline) { Set-PSReadlineOption -ContinuationPromptBackgroundColor $BackgroundColor Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'None' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Comment' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Keyword' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'String' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Operator' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Variable' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Command' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Type' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Number' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Member' Set-PSReadlineOption -BackgroundColor $BackgroundColor -TokenKind 'Parameter' Set-PSReadlineOption -EmphasisBackgroundColor $BackgroundColor Set-PSReadlineOption -ErrorBackgroundColor $BackgroundColor } } #endregion Set Background Color #region Set Window Title if (Test-PSFParameterBinding -ParameterName "WindowTitle") { $host.ui.rawui.Windowtitle = $WindowTitle } #endregion Set Window Title #region Set Window Width if (Test-PSFParameterBinding -ParameterName "WindowWidth") { try { Set-ShellWindowWidth -WindowWidth $WindowWidth -ErrorAction Stop } catch { Stop-PSFFunction -Message "Failed to set window width to $WindowWidth" -EnableException $EnableException -ErrorRecord $_ -Tag 'fail', 'width', 'console', 'window' return } } #endregion Set Window Width } function Start-PSUTimer { <# .SYNOPSIS Creates a timer that will alarm the user after it has expired. .DESCRIPTION Creates a timer that will alarm the user after it has expired. Provides both visual and sound warnings. Also provides a progress bar with a time remaining display. .PARAMETER Duration The time to wait. .PARAMETER Message What to wait for. .PARAMETER NoProgress Disables progress bar. .PARAMETER AlarmInterval In what time interval to write warnings and send sound. .PARAMETER AlarmCount How often to give warning. .EXAMPLE PS C:\> timer 170 Tea After 170 Duration give warning that the tea is ready. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory = $true)] [Alias('Seconds')] [PSFDateTime] $Duration, [Parameter(Position = 1, Mandatory = $true)] $Message, [switch] $NoProgress, [int] $AlarmInterval = 250, [int] $AlarmCount = 25 ) begin { Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param' $start = Get-Date $end = $Duration.Value function Get-FriendlyTime { [CmdletBinding()] param ( [int] $Seconds ) $tempSeconds = $Seconds $strings = @() if ($tempSeconds -gt 3599) { [int]$count = [math]::Floor(($tempSeconds / 3600)) $strings += "{0}h" -f $count $tempSeconds = $tempSeconds - ($count * 3600) } if ($tempSeconds -gt 59) { [int]$count = [math]::Floor(($tempSeconds / 60)) $strings += "{0}m" -f $count $tempSeconds = $tempSeconds - ($count * 60) } $strings += "{0}s" -f $tempSeconds $strings -join " " } } process { if (-not $NoProgress) { Write-Progress -Activity "Waiting for $Message" -Status "Starting" -PercentComplete 0 } while ($end -gt (Get-Date)) { Start-Sleep -Milliseconds 500 if (-not $NoProgress) { $friendlyTime = Get-FriendlyTime -Seconds ($end - (Get-Date)).TotalSeconds [int]$percent = ((Get-Date) - $start).TotalSeconds / ($end - $start).TotalSeconds * 100 Write-Progress -Activity "Waiting for $Message" -Status "Time remaining: $($friendlyTime)" -PercentComplete ([System.Math]::Min($percent, 100)) } } if (-not $NoProgress) { Write-Progress -Activity "Waiting for $Message" -Completed } $countAlarm = 0 while ($countAlarm -lt $AlarmCount) { Write-PSFMessage -Level Warning -Message "### $($Message) ###" [System.Console]::Beep(3000, $AlarmInterval) Start-Sleep -Milliseconds $AlarmInterval $countAlarm++ } } end { } } Import-PSUAlias -Name "timer" -Command "Start-PSUTimer" function Get-PSUPathAlias { <# .SYNOPSIS Gets the PSUPathAlias configuration values. .DESCRIPTION Gets the PSUPathAlias configuration values from the PSFConfig system. .PARAMETER Alias This is the name of the alias that you want for Set-PSUPath. Wildcards accepted Default Value: * .EXAMPLE PS C:\> Get-PSUPathAlias Returns all aliases #> [CmdletBinding()] param ( [string] $Alias = '*' ) $aliases = Get-PSFConfig -FullName psutil.pathalias.$Alias foreach ($alias in $aliases) { [pscustomobject]@{ Alias = ($alias.fullname -replace '^psutil.pathalias.') Path = $alias.value } } } function Remove-PSUPathAlias { <# .SYNOPSIS Removes a path alias fromm the configuration system. .DESCRIPTION Removes a path alias from the configuration system using Unregister-PSFConfig. Note: This command has no effect on configuration setings currently in memory. .PARAMETER Alias The name of the Alias that you want to remove from the configuration system. .EXAMPLE PS C:\> Remove-PSUPathAlias -Alias work Removes the path alias named work from the configuration system. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(ValuefromPipelineByPropertyName = $true)] $Alias ) process { Get-PSFConfig -FullName psutil.pathalias.$Alias | Unregister-PSFConfig Remove-PSFAlias -Name $Alias } } function Set-PSUPath { <# .SYNOPSIS Detects the alias that called it and sets the location to the corresponding path found in the configuration system. .DESCRIPTION Detects the alias that called it and sets the location to the corresponding path. This function will normally be called using an alias that gets set by using Set-PSUPathAlias. .PARAMETER Alias This is the name of the alias that called the command. Default Value is $MyInvocation.line and is detected automatically .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> Software PS C:\Software> In this example 'Software' is an alias for Set-PSUPath that was created by using Set-PSUPathAlias. Set-PSUPath detected that 'Software' was the alias that called it and then sends it to the path. It receives the path from Get-PSUPathAlias 'software' #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( $Alias = $MyInvocation.line, [switch] $EnableException ) Write-PSFMessage -Level InternalComment -Message "Bound parameters: $($PSBoundParameters.Keys -join ", ")" -Tag 'debug', 'start', 'param' try { $PSFConfigPath = Get-PSFConfigValue -FullName psutil.pathalias.$Alias -NotNull } catch { Stop-PSFFunction -Message "Unable to find a path setting for the alias" -Category InvalidOperation -Tag fail -Exception $_ return } try { if ($PSFConfigPath -contains '$env:') { Write-PSFMessage "Environmental variable detected, resolving path" -Level internalcomment Set-Location (Resolve-Path $PSFConfigPath) } else { Set-Location $PSFConfigPath } } catch { $psfFuncParams = @{ Message = "Unable to set location to $PSFConfigPath" Category = 'InvalidOperation' Tag = 'fail' ErrorRecord = $_ EnableException = $EnableException } Stop-PSFFunction @psfFuncParams return } } function Set-PSUPathAlias { <# .SYNOPSIS Used to create an an alias that sets your location to the path you specify. .DESCRIPTION A detailed description of the Set-PSUPathAlias function. .PARAMETER Alias Name of the Alias that will be created for Set-PSUPath. Set-PSU Path detects the alias that called it and then finds the corresponding PSFConfig entry for it. .PARAMETER Path This is the path that you want your location to change to when the alias is called. .PARAMETER Register Causes PSUtil to remember the alias across sessions. For more advanced options, see Register-PSFConfig. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE PS C:\> Set-PSUPathAlias -Alias 'work' -Path 'C:\work' Creates an alias to Set-PSUPath that will set the location to 'c:\work' .EXAMPLE PS C:\> Set-PSUPathAlias -Alias 'repos' -Path 'C:\repos' -Register Creates an alias for repos and registers the setting so that it will persist between sessions. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string] $Alias, [Parameter(Position = 1, Mandatory)] [string] $Path, [switch] $Register, [switch] $EnableException ) try { Set-PSFConfig -FullName psutil.pathalias.$Alias -Value $Path -Description 'Sets an alias for Set-PSUPath that takes you to the path specified in the value.' } catch { $stopParams = @{ Message = 'Error encountered. Alias not set' Category = 'InvalidOperation' Tag = 'Fail' ErroRecord = $_ EnableException = $EnableException } Stop-PSFFunction @stopParams return } if ($Register) { Get-PSFConfig -FullName psutil.pathalias.$Alias | Register-PSFConfig } try { Import-PSUAlias -Name $Alias -Command Set-PSUPath } catch { $stopParams = @{ Message = 'Error. Alias not set' Category = 'InvalidOperation' Tag = 'Fail' ErroRecord = $_ EnableException = $EnableException } Stop-PSFFunction @stopParams return } } function Add-PSUString { <# .SYNOPSIS Makes it easy to add content to a string at pipeline. .DESCRIPTION Makes it easy to add content to a string at pipeline. .PARAMETER InputString The string(s) to add content to .PARAMETER Before What is prepended to the input string. .PARAMETER After What is appended to the input string .EXAMPLE 1..10 | Add-PSUString "srv-ctx" "-dev" Returns a set of strings from 'srv-ctx1-dev' through 'srv-ctx10-dev' #> [OutputType([System.String])] [CmdletBinding()] Param ( [Parameter(ValueFromPipeline = $true)] [string[]] $InputString, [Parameter(Position = 0)] [string] $Before = "", [Parameter(Position = 1)] [string] $After = "" ) begin { } process { foreach ($line in $InputString) { "$($Before)$($line)$($After)" } } end { } } Import-PSUAlias -Name add -Command Add-PSUString Import-PSUAlias -Name wrap -Command Add-PSUString function Format-PSUString { <# .SYNOPSIS Allows formatting objects into strings. .DESCRIPTION Allows formatting objects into strings. This is equivalent to the '-f' operator, but supports input from pipeline. .PARAMETER InputObject The object to format .PARAMETER Format The format to apply to the object .PARAMETER LotSize Default: 1 How many inputo bjects should be packed into the same format string. .EXAMPLE 1..5 | format "foo {0:D2}" returns "foo 01" through "foo 05" .EXAMPLE 1..6 | format "foo {0:D3}-{1:D3}" -LotSize 2 returns "foo 001-002","foo 003-004","foo 005-006" #> [OutputType([System.String])] [CmdletBinding()] Param ( [Parameter(ValueFromPipeline = $true)] $InputObject, [Parameter(Position = 0, Mandatory = $true)] [string] $Format, [int] $LotSize = 1 ) begin { $values = @() } process { foreach ($item in $InputObject) { $values += $item if ($values.Count -ge $LotSize) { $Format -f $values $values = @() } } } end { if ($values.Count -gt 0) { $Format -f $values } } } Import-PSUAlias -Name format -Command Format-PSUString function Join-PSUString { <# .SYNOPSIS Joins input strings together. .DESCRIPTION Joins input strings together. .PARAMETER InputString The strings to join .PARAMETER With What to join the strings with. .PARAMETER BatchSize Default: 0 How many to join together at a time. If 0 or lower are specfied, all strings will be joined together. Otherwise, it will join [BatchSize] strigns together at a time. .EXAMPLE 1..9 | join "," Returns "1,2,3,4,5,6,7,8,9" .EXAMPLE 1..9 | join "," -BatchSize 5 Returns "1,2,3,4,5", "6,7,8,9" #> [OutputType([System.String])] [CmdletBinding()] Param ( [Parameter(ValueFromPipeline = $true)] [string[]] $InputString, [Parameter(Position = 0)] [string] $With = ([System.Environment]::NewLine), [int] $BatchSize ) begin { $lines = @() } process { foreach ($line in $InputString) { $lines += $line if (($BatchSize -gt 0) -and ($lines.Count -ge $BatchSize)) { $lines -join $With $lines = @() } } } end { $lines -join $With } } Import-PSUAlias -Name join -Command Join-PSUString function Remove-PSUString { <# .SYNOPSIS Trims a string. .DESCRIPTION Trims a string. .PARAMETER InputString The string that will be trimmed .PARAMETER What What should be trimmed? .PARAMETER Start Should only the start be trimmed? .PARAMETER End Should only the end be trimmed? .EXAMPLE Get-Content file.txt | trim Retrieves all content from file.txt, then trims each line. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [OutputType([System.String])] [CmdletBinding()] Param ( [Parameter(ValueFromPipeline = $true)] [string[]] $InputString, [Parameter(Position = 0)] [string] $What = "", [switch] $Start, [switch] $End ) begin { } process { foreach ($line in $InputString) { if ($Start -and (-not $End)) { $line.TrimStart($What) } elseif ((-not $Start) -and $End) { $line.TrimEnd($What) } else { $line.Trim($What) } } } end { } } Import-PSUAlias -Name trim -Command Remove-PSUString function Set-PSUString { <# .SYNOPSIS Replaces a part of the input string with another. .DESCRIPTION Replaces a part of the input string with another. Supports both regex replace as well as regular .replace(). .PARAMETER InputString The stringgs on which replacement will be performed. .PARAMETER What What should be replace? .PARAMETER With With what should it be replaced? .PARAMETER Simple By default, this function uses regex replace. Sometimes this may not be desirable. This switch enforces simple replacement, not considering any regular expression functionality. .PARAMETER Options Default: IgnoreCase When using regex replace, it may become desirable to specify options to the system. .PARAMETER EnableException Replaces user friendly yellow warnings with bloody red exceptions of doom! Use this if you want the function to throw terminating errors you want to catch. .EXAMPLE "abc ABC" | replace b d Returns "adc AdC". .EXAMPLE "abc ABC" | replace b d -Options None Returns "adc ABC" .EXAMPLE "abc \def" | replace "\de" "&ed" -s Returns "abc &edf" #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] [OutputType([System.String])] [CmdletBinding(DefaultParameterSetName = "regex")] Param ( [Parameter(ValueFromPipeline = $true)] [string[]] $InputString, [Parameter(Position = 0, Mandatory = $true)] [string] $What, [Parameter(Position = 1, Mandatory = $true)] [AllowEmptyString()] [object] $With, [Parameter(ParameterSetName = "Simple")] [Alias('s')] [switch] $Simple, [Parameter(ParameterSetName = "Regex")] [System.Text.RegularExpressions.RegexOptions] $Options = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase, [switch] $EnableException ) begin { if ($With -isnot [System.Management.Automation.ScriptBlock]) { $With = "$With" } elseif ($Simple) { Stop-PSFFunction -Message "Cannot do a lambda replace with a simple string replacement. Please specify a string or remove the simple parameter." -EnableException $EnableException -Category InvalidArgument -Tag 'fail','validate' return } } process { if (Test-PSFFunctionInterrupt) { return } foreach ($line in $InputString) { try { if ($Simple) { $line.Replace($What, $With) } else { [regex]::Replace($line, $What, $With, $Options) } } catch { Stop-PSFFunction -Message "Failed to replace line" -EnableException $EnableException -ErrorRecord $_ -Tag 'Fail', 'record' -Continue } } } end { } } Import-PSUAlias -Name replace -Command Set-PSUString function Split-PSUString { <# .SYNOPSIS Splits a string. In a pipeline. .DESCRIPTION Splits a string. In a pipeline. .PARAMETER InputString The string(s) to split .PARAMETER With Default: "`n" What to split the string with .PARAMETER Simple Whether to disable regex when splitting. .PARAMETER Options Regex options to consider .EXAMPLE "abc,def" | split "," Returns "abc","def" #> [OutputType([System.String[]])] [CmdletBinding(DefaultParameterSetName = "Regex")] Param ( [Parameter(ValueFromPipeline = $true)] [string[]] $InputString, [Parameter(Position = 0)] [string] $With = "`n", [Alias('r')] [Parameter(ParameterSetName = "Simple")] [switch] $Simple, [Parameter(ParameterSetName = "Regex")] [System.Text.RegularExpressions.RegexOptions] $Options = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase ) begin { $IsRegex = $PSCmdlet.ParameterSetName -eq "Regex" } process { foreach ($line in $InputString) { if ($IsRegex) { [regex]::Split($line, $With, $Options) } else { $line.Split($With) } } } end { } } Import-PSUAlias -Name split -Command Split-PSUString Register-PSFTeppScriptblock -Name psutil-convert-object-from -ScriptBlock { [PSUtil.Object.ObjectHost]::Conversions.Values.From | Select-Object -Unique } Register-PSFTeppScriptblock -Name psutil-convert-object-to -ScriptBlock { [PSUtil.Object.ObjectHost]::Conversions.Values | Where-Object From -EQ $fakeBoundParameter.From | Expand-PSUObject -Name To } Register-PSFTeppScriptblock -Name PSUtil-Input-Object -ScriptBlock { [System.Management.Automation.Language.PipelineAst]$pipelineAst = $commandAst.parent $index = $pipelineAst.PipelineElements.IndexOf($commandAst) #region If it's the first command if ($index -lt 1) { return } #endregion If it's the first command $properties = @() $constraintsPositive = @() #region Process pre-commands $inputIndex = $index - 1 :main while ($true) { if ($pipelineAst.PipelineElements[$inputIndex].CommandElements) { # Resolve command and fail if it breaks $command = Get-Command $pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value -ErrorAction Ignore if ($command -is [System.Management.Automation.AliasInfo]) { $command = $command.ResolvedCommand } if (-not $command) { break } switch ($command.Name) { 'Where-Object' { $inputIndex = $inputIndex - 1; continue main } 'Tee-Object' { $inputIndex = $inputIndex - 1; continue main } #region Select-Object 'Select-Object' { $firstAst = $pipelineAst.PipelineElements[$inputIndex].CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ArrayLiteralAst] } | Select-Object -First 1 foreach ($element in $firstAst.Elements) { switch ($element.GetType().FullName) { 'System.Management.Automation.Language.StringConstantExpressionAst' { $constraintsPositive += $element.Value if ($element.Value -notmatch "\*") { $properties += $element.Value } } 'System.Management.Automation.Language.HashtableAst' { $constraintsPositive += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"') $properties += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"') } } } $inputIndex = $inputIndex - 1; continue main } #endregion Select-Object #region Select-PSFObject 'Select-PSFObject' { $firstAst = $pipelineAst.PipelineElements[$inputIndex].CommandElements | Where-Object { $_ -is [System.Management.Automation.Language.ArrayLiteralAst] } | Select-Object -First 1 foreach ($element in $firstAst.Elements) { switch ($element.GetType().FullName) { "System.Management.Automation.Language.StringConstantExpressionAst" { $par = [PSFramework.Parameter.SelectParameter]$element.Value if ($par.Value -match "\*") { $constraintsPositive += $par.Value } else { if ($par.Value -is [System.String]) { $properties += $par.Value $constraintsPositive += $par.Value } else { $properties += $par.Value["Name"] $constraintsPositive += $par.Value["Name"] } } } "System.Management.Automation.Language.HashtableAst" { $properties += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"') $constraintsPositive += ($element.KeyValuePairs | Where-Object Item1 -Match '^N$|^Name$' | Select-Object -First 1).Item2.ToString().Trim('"') } } } $inputIndex = $inputIndex - 1; } #endregion Select-PSFObject default { break main } } } else { break } } # Catch moving through _all_ options in the pipeline if ($inputIndex -lt 0) { return $properties } #endregion Process pre-commands #region Input from command if ($pipelineAst.PipelineElements[$inputIndex].CommandElements) { if ($command = Get-Command $pipelineAst.PipelineElements[$inputIndex].CommandElements[0].Value -ErrorAction Ignore) { switch ($command.Name) { #region Default for commands default { foreach ($type in $command.OutputType.Type) { switch ($type.GetType().FullName) { 'System.IO.FileInfo' { $properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name $properties += 'PSChildName', 'PSDrive', 'PSIsContainer', 'PSParentPath', 'PSPath', 'PSProvider', 'BaseName' break } 'System.IO.DirectoryInfo' { $properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name $properties += 'PSChildName', 'PSDrive', 'PSIsContainer', 'PSParentPath', 'PSPath', 'PSProvider', 'BaseName', 'VersionInfo' break } default { $properties += ($type.GetMembers("Instance, Public") | Where-Object MemberType -match "Field|Property").Name } } } } #endregion Default for commands } } } #endregion Input from command #region Input from Variable if ($pipelineAst.PipelineElements[$inputIndex].Expression -and $pipelineAst.PipelineElements[0].Expression[0].VariablePath) { $properties += ((Get-Variable -Name $pipelineAst.PipelineElements[0].Expression[0].VariablePath.UserPath -ValueOnly) | Select-Object -First 1 | Get-Member -MemberType Properties).Name } #endregion Input from Variable $properties | Select-Object -Unique | Sort-Object | ForEach-Object { if (-not $constraintsPositive) { $_ } foreach ($constraint in $constraintsPositive) { if ($_ -like $constraint) { $_ break } } } } Register-PSFTeppScriptBlock -Name 'PSUtil-Module-Installed' -ScriptBlock { (Get-InstalledModule).Name | Select-Object -Unique } Register-PSFTeppScriptBlock -Name 'PSUtil-Module-Total' -ScriptBlock { (Get-Module -ListAvailable).Name | Select-Object -Unique } Register-PSFTeppScriptblock -Name 'PSUtil-Module-Repository' -ScriptBlock { (Get-PSRepository).Name } Register-PSFTeppScriptblock -Name 'PSUtil-Module-PackageProvider' -ScriptBlock { (Get-PackageProvider).Name } Register-PSFTeppScriptblock -Name psutil-userprofile -ScriptBlock { Get-ChildItem "$env:SystemDrive\Users" -Force | Where-Object PSIsContainer | Expand-PSUObject Name } Register-PSFTeppArgumentCompleter -Command Invoke-PSUDesktop -Parameter User -Name psutil-userprofile Register-PSFTeppArgumentCompleter -Command Convert-PSUObject -Parameter From -Name psutil-convert-object-from Register-PSFTeppArgumentCompleter -Command Convert-PSUObject -Parameter To -Name psutil-convert-object-to #region Module Register-PSFTeppArgumentCompleter -Command Update-Module -Parameter Name -Name 'PSUtil-Module-Installed' Register-PSFTeppArgumentCompleter -Command Uninstall-Module -Parameter Name -Name 'PSUtil-Module-Installed' Register-PSFTeppArgumentCompleter -Command Install-Module -Parameter Name -Name 'PSUtil-Module-Total' Register-PSFTeppArgumentCompleter -Command Find-Command -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Find-DscResource -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Find-Module -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Find-RoleCapability -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Find-Script -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Install-Module -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Install-Script -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Publish-Module -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Publish-Script -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Save-Module -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Save-Script -Parameter Repository -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Get-PSRepository -Parameter Name -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Unregister-PSRepository -Parameter Name -Name 'PSUtil-Module-Repository' Register-PSFTeppArgumentCompleter -Command Register-PSRepository -Parameter PackageManagementProvider -Name 'PSUtil-Module-PackageProvider' #endregion Module #region Select Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter Property -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ExpandProperty -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ExcludeProperty -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ShowProperty -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-PSFObject -Parameter ShowExcludeProperty -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter Property -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter ExpandProperty -Name PSUtil-Input-Object Register-PSFTeppArgumentCompleter -Command Select-Object -Parameter ExcludeProperty -Name PSUtil-Input-Object #endregion Select New-PSFLicense -Product 'PSUtil' -Manufacturer 'Friedrich Weinmann' -ProductVersion $script:ModuleVersion -ProductType Module -Name MIT -Version "1.0.0.0" -Date (Get-Date "2018-12-16") -Text @" Copyright (c) 2018 Friedrich Weinmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "@ <# In this file, all default expansion definitions are stored. Wrap into a region each, with corresponding region label. #> #region Microsoft.PowerShell.Commands.MatchInfo [PSUtil.Object.ObjectHost]::ExpandedTypes["Microsoft.PowerShell.Commands.MatchInfo"] = { param ( $Object ) foreach ($item in $Object.Matches) { $item.Groups[1 .. ($item.Groups.Count - 1)].Value } } #endregion Microsoft.PowerShell.Commands.MatchInfo #region Microsoft.PowerShell.Commands.MemberDefinition [PSUtil.Object.ObjectHost]::ExpandedTypes["Microsoft.PowerShell.Commands.MemberDefinition"] = { param ( $Object ) $Object.Definition.Replace("), ", ")þ").Split("þ") } #endregion Microsoft.PowerShell.Commands.MemberDefinition #region System.Management.Automation.FunctionInfo [PSUtil.Object.ObjectHost]::ExpandedTypes["System.Management.Automation.FunctionInfo"] = { param ( $Object ) @" function $($Object.Name) { $($Object.Definition) } "@ } #endregion System.Management.Automation.FunctionInfo #region System.Management.Automation.AliasInfo [PSUtil.Object.ObjectHost]::ExpandedTypes["System.Management.Automation.AliasInfo"] = { param ( $Object ) if ($Object.ResolvedCommand.CommandType -eq "Function") { @" function $($Object.ResolvedCommand.Name) { $($Object.ResolvedCommand.Definition) } "@ } else { $Object.ResolvedCommand } } #endregion System.Management.Automation.AliasInfo # The king of aliases Import-PSUAlias -Name "grep" -Command "Select-String" # Add simple aliases for everyday cmdlets Import-PSUAlias -Name "a" -Command "Get-Alias" Import-PSUAlias -Name "c" -Command "Get-Command" Import-PSUAlias -Name "m" -Command "Measure-Object" Import-PSUAlias -Name "v" -Command "Get-Variable" # Add aliases for frequent export commands Import-PSUAlias -Name "ix" -Command "Import-PSFClixml" Import-PSUAlias -Name "ex" -Command "Export-PSFClixml" Import-PSUAlias -Name "ic" -Command "Import-Csv" Import-PSUAlias -Name "ec" -Command "Export-Csv" # Add alias for easy clipboarding Import-PSUAlias -Name "ocb" -Command "Set-Clipboard" # Add alias for creating object Import-PSUAlias -Name "new" -Command "New-Object" # Add alias for the better select and to avoid breaking on old command Import-PSUAlias -Name "spo" -Command "Select-PSFObject" Import-PSUAlias -Name "Select-PSUObject" -Command "Select-PSFObject" if (Get-PSFConfigValue -FullName 'PSUtil.Import.Alias.SystemOverride') { Remove-PSFAlias -Name select -Force Remove-PSFAlias -Name gm -Force Import-PSUAlias -Name select -Command 'Select-PSFObject' Import-PSUAlias -Name gm -Command 'Get-PSMDMember' } if ((Get-PSFConfigValue -FullName "PSUtil.Import.Keybindings" -Fallback $true) -and (Get-Module PSReadline)) { foreach ($file in (Get-ChildItem -Path (Join-PSFPath $script:ModuleRoot 'internal' 'keybindings'))) { . Import-ModuleFile -Path $file.FullName } } Set-PSFTypeAlias -Mapping @{ "PSUSelectParameter" = "PSUtil.Parameter.SelectParameter" } Register-PSUObjectConversion -From dec -To bin -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToString($InputObject, 2) } Register-PSUObjectConversion -From bin -To dec -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToInt32($InputObject, 2) } Register-PSUObjectConversion -From dec -To hex -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToString($InputObject, 16) } Register-PSUObjectConversion -From hex -To dec -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToInt32($InputObject, 16) } Register-PSUObjectConversion -From dec -To oct -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToString($InputObject, 8) } Register-PSUObjectConversion -From oct -To dec -ScriptBlock { Param ( $InputObject ) [System.Convert]::ToInt32($InputObject, 8) } Register-PSUObjectConversion -From hex -To bin -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 16) [System.Convert]::ToString($temp, 2) } Register-PSUObjectConversion -From bin -To hex -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 2) [System.Convert]::ToString($temp, 16) } Register-PSUObjectConversion -From hex -To oct -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 16) [System.Convert]::ToString($temp, 8) } Register-PSUObjectConversion -From oct -To hex -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 8) [System.Convert]::ToString($temp, 16) } Register-PSUObjectConversion -From bin -To oct -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 2) [System.Convert]::ToString($temp, 8) } Register-PSUObjectConversion -From oct -To bin -ScriptBlock { Param ( $InputObject ) $temp = [System.Convert]::ToInt32($InputObject, 8) [System.Convert]::ToString($temp, 2) } Register-PSUObjectConversion -From script -To encoded -ScriptBlock { Param ( $InputObject ) $bytes = [System.Text.Encoding]::Unicode.GetBytes($InputObject) [Convert]::ToBase64String($bytes) } Register-PSUObjectConversion -From encoded -To script -ScriptBlock { Param ( $InputObject ) $bytes = [System.Convert]::FromBase64String($InputObject) [System.Text.Encoding]::Unicode.GetString($bytes) } #endregion Load compiled code |