Library.psm1
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Write-Host "Module Library.psm1 removed on $(Get-Date)" } $PRIVATE:PrivateData = $MyInvocation.MyCommand.Module.PrivateData #Available to all functions below. function Run([String]$scriptName = '-BLANK-') { <# .SYNOPSIS Record start and end times of any scripts that are run during this session in the Scripts Event Log. .DESCRIPTION This function records any running scripts in the Scripts Event Log. Start scripts with 'Run xxxx.ps1' to capture. Use 'Get-Help Run -Online' to access any required Windows services mentioned here. .LINK Http://www.SeaStar.co.nf #> if ($host.Name -ne 'ConsoleHost') { #Use Run-Script on ISE Host. return } $logfile = "$env:programfiles\Sea Star Development\" + "Script Monitor Service\ScriptMon.txt" $parms = $myInvocation.Line -replace "run(\s+)$scriptName(\s*)" $script = $scriptName -replace "\.ps1\b" #Replace from word end only. $script = $script + ".ps1" If (Test-Path $pwd\$script) { If(!(Test-Path Variable:\Session.Script.Job)) { Set-Variable Session.Script.Job -value 1 -scope global ` -description "Script counter" } $Job = Get-Variable -name Session.Script.Job $number = $job.Value.ToString().PadLeft(4,'0') $startTime = Get-Date -Format "dd/MM/yyyy HH:mm:ss" $tag = "$startTime [$script] start. --> $($myInvocation.Line)" If (Test-Path $logfile) { $tag | Out-File $logfile -encoding 'Default' -Append } Write-EventLog -Logname Scripts -Source Monitor -EntryType Information -EventID 2 -Category 001 -Message "Script Job: $script (PS$number) started." Invoke-Expression -command "$pwd\$script $parms" $endTime = Get-Date -Format "dd/MM/yyyy HH:mm:ss" $tag = "$endTime [$script] ended. --> $($myInvocation.Line)" If (Test-Path $logfile) { $tag | Out-File $logfile -encoding 'Default' -Append } Write-Eventlog -Logname Scripts -Source Monitor -EntryType Information -EventID 1 -Category 001 -Message "Script Job: $script (PS$number) ended." #The next line is only needed in case any script itself updates this value. $Job = Get-Variable -name Session.Script.Job if ($job.Value.ToString().PadLeft(4,'0') -eq $number) { $job.Value += 1 #Only increment here if not done elsewhere. } } else { Write-Error "$pwd\$script does not exist." } } function backup { #Edit to suit. This will fail if selected function is not available. Invoke-Expression "Select-Backup $pwd,$pwd\modules\modem,$pwd\modules\ebooks,$pwd\modules\library ps1,psm1,psd1,xml,xaml -exclude cd -interval $args" } function killx { <# .SYNOPSIS Delete duplicate instances of Windows Explorer running. .DESCRIPTION Often Windows Explorer can leave multiple instances in memory thereby wasting system resources. #> $exCount = 0 $exCount = @(Get-Process -ErrorAction SilentlyContinue explorer).count if ($exCount -gt 1) { Get-Process explorer | Sort-Object -descending CPU | Select-Object -Skip 1 ID | ForEach-Object -Process {Stop-Process -Id $_.ID} Write-Warning "$($exCount-1) instance(s) of Windows Explorer terminated." } } function Get-SessionIDs { #NOTE: This will not now run the last selected Id on exiting. $date = "{0:F}" -f [DateTime]::Now $selected = Get-History -Count 100 | Sort commandLine -Unique | sort id | Select @{name='Execution Time';expression={$_.EndExecutionTime}}, ` @{name='Id';expression={"{0:D3}" -f $_.Id}}, CommandLine | Out-GridView -Title "$date Session Command History" -outputMode Single if ($null -eq $selected) { return #Cancel or X was clicked. } else { #OK was selected, so run chosen history Id. $selected | Invoke-History } } function Debug-Regex { <# .SYNOPSIS Debug a Regex search string and show any 'Match' results. .DESCRIPTION Sometimes it is easier to correct any regex usage if each match can be shown in context. This function will show each successful result in a separate colour, including the strings both before and after the match. .EXAMPLE Debug-Regex '\b[A-Z]\w+' 'Find capitalised Words in This string' -first Use the -F switch to return only the first match. .EXAMPLE Debug-Regex '\b[0-9]+\b' 'We have to find numbers like 123 and 456 in this' .EXAMPLE PS >$a = Get-Variable regexDouble -ValueOnly PS >db $a 'Find some double words words in this this string' .NOTES Based on an idea from the book 'Mastering Regular Expressions' by J.Friedl, page 429. #> param ([regex]$regex = '(?i)(A|B)\d', [string]$string = 'ABC B1 a1 A1 B6 d2', [switch]$first) $m = $regex.match($string) if (!$m.Success) { Write-Host "No Match using Regex '$regex'" -Fore Cyan return } $count = 1 Write-Host "MATCHES [--------------<" -Fore Cyan -NoNewLine Write-Host "match" -Fore White -NoNewLine Write-Host ">-------------------]" -Fore Cyan while ($m.Success) { Write-Host "$count $($m.result('[$`<'))" -Fore Cyan -NoNewLine Write-Host "$($m.result('$&'))" -Fore White -NoNewLine Write-Host "$($m.result('>$'']'))" -Fore Cyan if ($first) { return } $count++ $m = $m.NextMatch() } Write-Host "MATCHES above using regex [-<" -Fore Cyan -NoNewLine Write-Host $regex -Fore White -NoNewLine Write-Host ">-]" -Fore Cyan } function Get-Function { <# .SYNOPSIS Find any Function in a file. .DESCRIPTION Use 'Get-ChildItem' with a filter to pipe values to 'Select-String' in order to extract the Name and Line number. .EXAMPLE Get-Function 'debug(.+)\b' -recurse #> param ([Parameter(mandatory=$true)][regex]$pattern, [string]$path = $pwd, [string]$filter = '*.ps*', [switch]$recurse = $false) if ($filter -notmatch '\*\.ps(m?1|\*)') { Write-Warning "Filter '$filter' is invalid -resubmit" return } Get-ChildItem $path -Filter $filter -recurse:$recurse | Select-String -Pattern '\bfunction\b' | ForEach-Object { $tokens = [Management.Automation.PSParser]::Tokenize($_.Line,[ref]$null) $( foreach ($token in $tokens) { if ($token.Type -eq ‘Keyword’ -and $token.Content -match ‘function’) { do { $more = $foreach.MoveNext() } until ($foreach.Current.Type -eq ‘CommandArgument’ -or !$more) New-Object PSObject -Property @{ Filename = $_.Path #From Select-String values. Line = $_.LineNumber FunctionName = $foreach.Current.Content #CommandArgument. } | Select-Object FunctionName, FileName, Line } } ) | Where-Object {$_.FunctionName -match '(?i)' + $pattern} } } function Show-Usage { <# .SYNOPSIS Show usage of certain console commands. .DESCRIPTION Scan the console Transcript file and count the non-PowerShell commands used. List the results in a table in descending order. Use the exported alias 'summary'. All commands and aliases from the Library module will be found, so only add extra parameters for those not there, ie 'summary import-module'. Multiple parameters must be separated by commas. .EXAMPLE PS >summary With no input parameters the Transcript file is scanned for any use of any exported commands or aliases. Count Name ----- ---- 28 summary 11 killx 5 backup 3 add 2 ff 1 show-usage 1 addTime 1 gh .EXAMPLE PS >summary publish-Module, Import-Module The same as a default search, but with two extra parameters for commands that have been used, which can be anything entered in the PowerShell console. Unused parameters will be ignored in the search. Count Name ----- ---- 48 import-module 29 summary 11 killx 5 backup 3 publish-module 3 add 2 ff 1 show-usage 1 addTime 1 gh .NOTES All commands exported by the Library module will be included in the default search pattern, including any aliases. To use an extra parameter just add it to the Show-Usage (or summary) command with multiple parameters being separated by commas. See examples for details. #> param([Array]$data, #Input string comma separated. $PRIVATE:extra = 'find-anything\b|') #Just a blank parameter here. if ($data -ne $null) { $data | foreach { $PRIVATE:extra+= $_ + "\b|" } #Now we add $extra to the default pattern first. } #Followed by any aliases. [Array]$aliases = (Get-Module library).exportedAliases | foreach {$_.Keys} $aliases | foreach { $PRIVATE:extra+= $_ + "\b|" } $pattern = get-command -Module library | ForEach-Object -begin {$pattern = $extra} ` -process {$pattern += $_.Name + "\b|"} ` -end {$pattern -replace "\|$", ""} if (Test-Path .\transcript.txt) { Select-String -pattern "^PS" -path .\transcript.txt | select-object line | Select-String -pattern $pattern -allMatches | ForEach-Object -process { $_.Matches } | Group-Object value -NoElement | Sort-Object count -Descending } else { Write-Warning "File 'transcript.txt' not found. Use 'Start-Transcript .\transcript.txt' to create." } } function Get-ZIPfiles { <# .SYNOPSIS Search for (filename) strings inside compressed ZIP or RAR files (V2.8). .DESCRIPTION In any directory containing a large number of ZIP/RAR compressed files this procedure will search each individual file name for simple text strings, listing both the source RAR/ZIP file and the individual file name containing the string. The relevant RAR/ZIP can then be subsequently opened in the usual way. PS V3 will now use the OK button to open the selected folder with WinRAR. The parameters -Table or -Gridview may be used to produce any output in tabular format. .EXAMPLE extract -path d:\scripts -find 'library' Searching for 'library'... (Use CTRL+C to quit) [Editor.zip] My Early Life In The School Library.html [Editor.rar] Using Library procedures in Win 7.mht [Test2.rar] Playlists from library - Windows 7 Forums.mht [Test3.rar] Module library functions UserGroup.pdf Folder 'D:\Scripts' contains 4 matches for 'library' in 4 file(s). .EXAMPLE extract pdf desk Searching for 'pdf' - please wait... (Use CTRL+C to quit) [Test1.rar] VirtualBox_ Guest Additions package.pdf [Test2.rar] W8 How to Install Windows 8 in VirtualBox.pdf [Test2.rar] W8 Install Windows 8 As a VM with VirtualBox.pdf Folder 'C:\Users\Sam\desktop' contains 3 matches for 'pdf' in 2 file(s). This example uses the 'extract' alias to find all 'pdf' files on the desktop. .NOTES The first step will find any lines containing the selected pattern (which can be anywhere in the line). Each of these lines will then be split into 2 headings: Source and Filename. .LINK Http://www.SeaStar.co.nf #> [CmdletBinding()] param([string][Parameter(Mandatory=$true)]$find, [string][ValidateNotNullOrEmpty()]$path = $pwd, [switch][alias("GRIDVIEW")]$table) Set-StrictMode -Version 2 switch -wildcard ($path) { 'desk*' { $path = Join-Path $home 'desktop\*' ; break } 'doc*' { $docs = [environment]::GetFolderPath("mydocuments") $path = Join-Path $docs '*'; break } default { $xpath = Join-Path $path '*' -ea 0 if (!($?) -or !(Test-Path $path)) { Write-Warning "Path '$path' is invalid - resubmit" return } $path = $xpath } } Get-ChildItem $path -include '*.rar','*.zip' | Select-String -SimpleMatch -Pattern $find | foreach-Object ` -begin { [int]$count = 0 $container = @{} $lines = @{} $regex = '(?s)^(?<zip>.+?\.(?:zip|rar)):(?:\d+):.*(\\|/)(?<file>.*\.(mht|html?|pdf))(.*)$' Write-Host "Searching for '$find' - please wait... (Use CTRL+C to quit)" } ` -process { if ( $_ -match $regex ) { $container[$matches.zip] +=1 #Record the number in each. $source = Split-Path $matches.zip -Leaf $file = $matches.file $file = $file -replace '\p{S}|\p{Cc}',' ' #Some 'Dingbats'. $file = $file -replace '\s+',' ' #Single space words. if ($table) { $key = "{0:D4}" -f $count $lines["$key $source"] = $file #Create a unique key. } else { Write-Host "[$source] $file" } $count++ } } ` -end { $total = "in $($container.count) file(s)." $title = "Folder '$($path.Replace('\*',''))' contains $($count) matches for '$find' $total" if ($table -and $count -gt 0) { $lines.GetEnumerator() | Select-Object @{name = 'Source';expression = {$_.Key.SubString(5)}}, @{name = 'Match' ;expression = {$_.Value}} | Sort-Object Match | Out-GridView -Title $title -outputMode single | % {invoke-item "$($_.Source)"} } else { if ($count -eq 0) { $title = "Folder '$($path.Replace('\*',''))' contains no matches for '$find'." } Write-Host $title } } } #End function. function Use-Culture { <# .SYNOPSIS Use different cultures for commands. .DESCRIPTION Submit the required culture, such as fr-FR, together with any scriptblock containing the commands to be run. The default culture will be restored after the command completes. Get-Help Use-Culture -Online will produce a usable table of locale options. .EXAMPLE PS >Use-Culture es-ES {Get-Date} miércoles, 06 de noviembre de 2013 6:58:05 Shows the current date in Spanish. Entered without any parameters, ie Use-Culture, will show the current date in Danish. .EXAMPLE PS >Use-Culture vi-VN {Get-Date} 17 Tha´ng Muo'i Mô?t 2017 9:17:32 CH Shows the current date in Vietnamese. Any untranslatable characters are shown as '?'. .NOTES This is an example from the book 'Windows PowerShell Cookbook' by Lee Holmes, page 220. .LINK https://msdn.microsoft.com/en-us/library/ee825488(v=cs.20).aspx #> param( [System.Globalization.CultureInfo]$culture = 'da-DK', [ScriptBlock]$script= {Get-Date}) function Set-Culture([System.Globalization.CultureInfo]$culture) { [System.Threading.Thread]::CurrentThread.CurrentUICulture = $culture [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture } $oldCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture trap { Set-Culture $oldCulture } #Restore original culture if script has errors. [System.Threading.Thread]::CurrentThread.CurrentCulture = $culture Set-Culture $culture Invoke-Command $script Set-Culture $OldCulture #Restore original culture information. } #End function Use-Culture. function AddTime ([String]$hhmmss) { <# .SYNOPSIS Add times together in the format 00:00:00, ie 'hh:mm:ss' and return the result. .DESCRIPTION There are many occasions when this cannot be done with a standard Timespan command, for example when a total greater than 24 hours is involved. A subtraction can also be done with the SubtractTime function; and the total result can be reset to zero with the ClearTime function .EXAMPLE AddTime 23:00:00 will initially return '23:00:00' and then AddTime 02:10:12 will return '25:10:12'. SubtractTime 10:20:12 will return 14:50:00. Negative results can appear if the result is less than 00:00:00 in the form '-02:12:59'. .EXAMPLE PS >AddTime 13:45:00 Result: 13:45:00 PS >AddTime 06:10:45 Result: 19:55:45 .NOTES Used in the Get-Modem module to add internet Connect and Disconnect event times from the Internet Explorer Event Log. .LINK http://www.SeaStar.co.nf #> $PRIVATE:PrivateData = $MyInvocation.MyCommand.Module.PrivateData [Int]$h = $MyInvocation.MyCommand.Module.PrivateData['h'] [Int]$m = $MyInvocation.MyCommand.Module.PrivateData['m'] [Int]$s = $MyInvocation.MyCommand.Module.PrivateData['s'] if ($hhmmss -match '^\d+:[0-5][0-9]:[0-5][0-9]$') { $plus = $hhmmss.Split(':') $h+= $plus[0] $m+= $plus[1] $s+= $plus[2] if ($s -gt 59) { [Int]$min = $s/60 [Int]$sec = $s%60 $m+= $min $s = $sec } if ($m -gt 59) { [Int]$hour = $m/60 [Int]$min = $m%60 $h+= $hour $m = $min } #Now update global values before exit. $MyInvocation.MyCommand.Module.PrivateData['h'] = $h $MyInvocation.MyCommand.Module.PrivateData['m'] = $m $MyInvocation.MyCommand.Module.PrivateData['s'] = $s $clock = "{0:D3}:{1:D2}:{2:D2}" -f $h, $m, $s $content = '^(-?)(0)(\d+:\d\d:\d\d)$' $clock = $clock -replace $content, '$1$3' Write-Host "Result: $clock" } } #End function AddTime function SubtractTime ([String]$hhmmss) { $PRIVATE:PrivateData = $MyInvocation.MyCommand.Module.PrivateData [Int]$h = $MyInvocation.MyCommand.Module.PrivateData['h'] [Int]$m = $MyInvocation.MyCommand.Module.PrivateData['m'] [Int]$s = $MyInvocation.MyCommand.Module.PrivateData['s'] if ($hhmmss -match '^\d+:[0-5][0-9]:[0-5][0-9]$') { $minus = $hhmmss.Split(':') if (($s - [Int]$minus[2]) -lt 0) { $s+= (60 - [Int]$minus[2]) [Int]$minus[1]+= 1 } else { $s = $s - [Int]$minus[2] } if (($m - [Int]$minus[1]) -lt 0) { $m+= (60 - [Int]$minus[1]) [Int]$minus[0]+= 1 } else { $m = $m - [Int]$minus[1] } $h = $h - $minus[0] #Below we update global values before exit. $MyInvocation.MyCommand.Module.PrivateData['h'] = $h $MyInvocation.MyCommand.Module.PrivateData['m'] = $m $MyInvocation.MyCommand.Module.PrivateData['s'] = $s $clock = "{0:D3}:{1:D2}:{2:D2}" -f $h, $m, $s $content = '^(-?)(0)(\d+:\d\d:\d\d)$' $clock = $clock -replace $content,'$1$3' Write-Host "Result: $clock" } } #End function SubtractTime function ClearTime { $MyInvocation.MyCommand.Module.PrivateData['h'] = 0 $MyInvocation.MyCommand.Module.PrivateData['m'] = 0 $MyInvocation.MyCommand.Module.PrivateData['s'] = 0 } Set-Variable -Name regexDouble -value '\b(\w+)((?:\s|<[^>]+>)+)(\1\b)' ` -Description 'Find double words' Set-Variable -Name regexNumbers -value '\b([0-9]+)\b' ` -Description 'Find only numbers in a string' Set-Variable -Name regexMonth -value '[JFAMSOND](?# Lookbehind)(?:(?<=J)an|(?<=F)eb|(?<=M)a(r|y)|` (?<=A)pr|(?<=M)ay|(?<=J)u(n|l)|(?<=A)ug|(?<=S)ep|` (?<=O)ct|(?<=N)ov|(?<=D)ec)' ` -Description 'Short months but case sensitive.' New-Alias db Debug-Regex -Description 'Test Regex expressions' New-Alias ff Get-Function -Description 'Find all functions in scripts' New-Alias summary Show-Usage -Description 'List all console commands' New-Alias extract Get-ZIPfiles -Description 'Find files inside ZIP/RAR' New-Alias add AddTime -Description 'Add hh:mm:ss to total time' New-Alias sub SubtractTime -Description 'Subtract hh:mm:ss from total time' New-Alias reset ClearTime -Description 'Reset total time to zero' New-Alias uc Use-Culture -Description 'Use different cultures for commands' New-Alias gh Get-SessionIDs -Description 'Show the last 100 console commands' Export-ModuleMember -Function Get-SessionIDs, Get-Function, Run, killx, backup, GH, Debug-Regex, Show-Usage, ` Get-ZIPfiles, Use-Culture, AddTime, SubtractTime, ClearTime -Variable regex* -Alias gh, uc, db, ff, summary, extract, ` add, sub, reset |