Library.psm1
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Write-Host "Module Library.psm1 removed on $(Get-Date)" } $MyInvocation.MyCommand.ScriptBlock.Module.Description = "Contains functions Run, Backup, killx, Show-Usage, Extract, Debug-Regex" $PRIVATE:PrivateData = $MyInvocation.MyCommand.Module.PrivateData #Available to all functions below. function Run([String]$scriptName = '-BLANK-') { <# .SYNOPSIS Record any scripts that are run 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. #> 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\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 GH { $date = "{0:F}" -f [DateTime]::Now Get-History -Count 100 | Sort commandLine -Unique | sort id | Select @{name='Execution Time';expression={$_.EndExecutionTime}}, ` @{name='Id';expression={$_.Id}}, CommandLine | Out-GridView -Title "$date Session Command History" -outputMode Single | 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'. .NOTES Firstly set any names not in Library module ('-begin'). The '$pattern' will get all the commands, including aliases. #> $pattern = get-command -Module library | ForEach-Object -begin {$pattern = "gprs\b|Backup-ToUSB2\b|Get-GprsTime\b|"} ` -process {$pattern += $_.Name + "\b|"} ` -end {$pattern -replace "\|$", ""} 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 } 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. .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 ( [String][ValidatePattern('^[a-z]{2}(-[A-Z]{2})?$',Options='IgnoreCase')]$culture = 'en-US', [ScriptBlock]$script= {Get-Winevent -Logname Microsoft-Windows-TaskScheduler/Operational ` -MaxEvents 100 | Where {$_.Message -like "*fail*"} | fl message, TimeCreated} ) { <# .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. .EXAMPLE Use-Culture es-ES {Get-Date} Shows the current date in Spanish. miércoles, 06 de noviembre de 2013 6:58:05 .EXAMPLE $scriptblock = {Get-Winevent -Logname Microsoft-Windows-TaskScheduler/Operational -MaxEvents 100 | Where {$_.Message -like "*fail*"} | fl message, TimeCreated} Use-Culture en-US $scriptblock Message : Task Scheduler failed to start "\GoogleUpdateTaskMachineUA" task for user "NT AUTHORITY\System". Additional Data: Error Value: 2147750687. TimeCreated : 11/6/2013 6:44:51 AM (...) .NOTES This is an example from the book 'Windows PowerShell Cookbook' by Lee Holmes, page 220. #> 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' Export-ModuleMember -Function Get-Function, Run, killx, backup, GH, Debug-Regex, Show-Usage, ` Get-ZIPfiles, Use-Culture, AddTime, SubtractTime, ClearTime -Variable regex* -Alias db, ff, summary, extract, ` add, sub, reset |