Misc
This is my personal profile.ps1 which I usually install to my Windows boxes either system wide or in my home directory.
Installation
Powershell version 5+
Systemwide installation: c:\windows\system32\windowspowershell\v1.0\profile.ps1
Per user installation: c:\users\$($env:username)\documents\WindowsPowerShell\profile.ps1
Powershell version 7+
Systemwide installation: C:\Program Files\PowerShell\7\profile.ps1
Per user installation: C:\Users\$($env:username)\Documents\PowerShell\profile.ps1
Get all locations
$PROFILE | Select-Object *
Environment
Error handling
Maybe you want to set some preferences for your
$global:ErrorActionPreference = "Stop"
$global:WarningPreference = "SilentlyContinue"
$global:InformationPreference = "SilentlyContinue"
$global:VerbosePreference = "SilentlyContinue"
$global:DebugPreference = "SilentlyContinue"
$global:ProgressPreference = "SilentlyContinue"
$global:OutputEncoding = New-Object Text.Utf8Encoding -ArgumentList (,$false)
[Console]::OutputEncoding = $global:OutputEncoding
Prefer Actice Directory server
If you will write funtions that work on AD you might want to force your global catalog to not run into sync issues
> $gcAD = (Get-ADDomainController -Discover -Service GlobalCatalog `
-erroraction silentlycontinue) | select -expandproperty hostname
> $gcAD = "$($gcAD):3268"
> $PSDefaultParameterValues = @{
"*-AD*:Server" = "$($gcAD)"
}
Parse SSH personal config
I like to have my $env:USERPROFILE\.ssh\config parsed
Goal is to have the system names in my config available as command to ssh directly into that system
In Linux OS you simply create Aliases while in Windows an Alias is somehow different. Here you need to create functions, as Aliases don’t take parameters or such.
So, here’s my Windows Powershell solution for that
If you have an .ssh\config file it will parse it
if([System.IO.File]::Exists("$($env:USERPROFILE)\.ssh\config")) {
cat "$($env:USERPROFILE)\.ssh\config" | select-string "^host" | % {
$h = $_ -replace "host ",""
$s = $h.split(" ")
foreach($hst in $s) {
$cmd = "ssh $hst"
if($hst -eq $null -or $hst -eq "") { continue; }
New-Item -force -Path "Function:/$hst" -Value @"
$cmd
"@ 2>&1 | out-null
}
}
}
Keep the $cmd variable at the beginning of the line!
Regular aliases
if(!(get-alias l) 2>&1 | out-null) {
new-alias l Get-ChildItem
}
if(!(get-alias epss) 2>&1 | out-null) {
new-alias epss enter-pssession
}
if(!(get-alias npss) 2>&1 | out-null) {
new-alias npss new-pssession
}
if(!(get-alias wol) 2>&1 | out-null) {
new-alias wol Invoke-WakeOnLan
}
if(!(get-alias mtr) 2>&1 | out-null) {
new-alias mtr winmtr
}
if(!(get-alias grep) 2>&1 | out-null) {
new-alias grep select-string
}
# I normaly have curl installed so I delete the default
# Windows curl alias pointing to Invoke-Webrequest
if((get-alias curl) 2>&1 | out-null) {
Remove-Item Alias:curl -erroraction silentlycontinue | out-null
}
Handy functions
Snippets
# elevate powershell
function _elevatePrompt5($user) {
if($user -and $user -ne "") {
Start-Process powershell -Credential (get-credential $user)
} else {
Start-Process powershell -Verb RunAs
}
}
function _elevatePrompt7($user) {
if($user -and $user -ne "") {
Start-Process pwsh -Credential (get-credential $user)
} else {
Start-Process pwsh -Verb RunAs
}
}
function elevatePrompt($user) {
if($PSVersionTable.PSVersion.Major -eq 7) {
_elevatePrompt7($user)
} else {
_elevatePrompt5($user)
}
}
# File/Folder exists?
function dirExists($file) {
return [System.IO.Directory]::Exists($file)
}
function fileExists($file) {
return [System.IO.File]::Exists($file)
}
function exists($file) {
# test if file or directory
if((get-item $file -erroraction silentlycontinue).PSIsContainer) { return dirExists $file }
else { return fileExists $file }
}
# get uptime
function uptime() {
(get-date) - (gcim Win32_OperatingSystem).LastBootUpTime
}
# convert date to windows epoch
function get-windowsEpoch() {
param(
[parameter(Mandatory=$false,ValueFromPipeline=$True,`
ValueFromPipelineByPropertyName=$True,Position = 0)]
[DateTime]$date
)
try {
if ($date) {
$windowsEpoch = (Get-Date $date).ToFileTime()
} else {
$windowsEpoch = (Get-Date).ToFileTime()
}
return $windowsEpoch
} catch {
return $_
}
}
# convert windows epoch to date
function convert-windowsEpoch() {
#[datetime]::FromFileTime($_.pwdLastSet)
param(
[parameter(Mandatory=$true,ValueFromPipeline=$True,`
ValueFromPipelineByPropertyName=$True,Position = 0)]
$date
)
return [datetime]::FromFileTime($date)
}
# generate random passwords
function pwgen {
# define parameters
param([int]$PasswordLength = 20)
# ASCII Character set for Password
$CharacterSet = @{
Lowercase = (97..122) | Get-Random -Count 10 | % {[char]$_}
Uppercase = (65..90) | Get-Random -Count 10 | % {[char]$_}
Numeric = (48..57) | Get-Random -Count 10 | % {[char]$_}
SpecialChar = (33..47)+(58..64)+(91..96)+(123..126) `
| Get-Random -Count 10 | % {[char]$_}
}
# Frame Random Password from given character set
$StringSet = $CharacterSet.Uppercase + $CharacterSet.Lowercase `
+ $CharacterSet.Numeric + $CharacterSet.SpecialChar
$result = (-join(Get-Random -Count $PasswordLength -InputObject $StringSet))
return $result
}
# UUIDGEN / GUIDGEN
function uuidgen() {
[guid]::NewGuid().ToString().toupper()
}
function guidgen() { uuidgen; }
Wake on LAN
function Invoke-WakeOnLan
{
param
(
# one or more MACAddresses
[Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName)]
# mac address must be a following this regex pattern:
[ValidatePattern('^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$')]
[string[]]
$MacAddress
)
begin
{
# instantiate a UDP client:
$UDPclient = [System.Net.Sockets.UdpClient]::new()
}
process
{
foreach($_ in $MacAddress)
{
try {
$currentMacAddress = $_.toupper()
# get byte array from mac address:
$mac = $currentMacAddress -split '[:-]' |
# convert the hex number into byte:
ForEach-Object {
[System.Convert]::ToByte($_, 16)
}
#$mac = $mac.toupper()
#region compose the "magic packet"
# create a byte array with 102 bytes initialized to 255 each:
$packet = [byte[]](,0xFF * 102)
# leave the first 6 bytes untouched, and
# repeat the target mac address bytes in bytes 7 through 102:
6..101 | Foreach-Object {
# $_ is indexing in the byte array,
# $_ % 6 produces repeating indices between 0 and 5
# (modulo operator)
$packet[$_] = $mac[($_ % 6)]
}
#endregion
$IP = [System.Net.IPAddress]::Broadcast
$IP = New-Object Net.IPEndPoint $IP, 9
Write-Verbose "Connecting to $($IP.address.tostring())"
# connect to port 400 on broadcast address:
#$UDPclient.Connect($IP,9)
# send the magic packet to the broadcast address:
$null = $UDPclient.Send($packet, $packet.Length, $IP)
Write-Verbose "sent magic packet to $currentMacAddress..."
Write-Verbose "Packet:`n$($packet)"
} catch {
Write-Warning "Unable to send ${mac}: $_"
}
}
}
end
{
# release the UDP client and free its memory:
$UDPclient.Close()
$UDPclient.Dispose()
}
}
Merge-PDF
For this to work you need to install Ghostscript x64 for windows
function Merge-PDF($filepattern) {
if($gs = Get-Command gswin64c) {
& "$($gs)" -dNOPAUSE -sDEVICE=pdfwrite -dBATCH `
-sOUTPUTFILE="merged.pdf" $(ls $filepattern).fullname
} else {
write-error "You need to install Ghostscript for windows!"
return
}
}
Split file
function Split-File() {
<#
.SYNOPSIS
Splits a file into multiple parts
.DESCRIPTION
Splits a file into smaller parts. The maximum size of the part files can be specified. The number of parts required is calculated.
.EXAMPLE
Split-File -Path 'c:\test.zip' -PartSizeBytes 2.5MB
Splits the file c:\test.zip in as many parts as necessary. Each part file is no larger than 2.5MB
.EXAMPLE
Split-File -Path 'c:\test.zip' -PartSizeBytes 2.5MB -AddSelfExtractor
Splits the file c:\test.zip in as many parts as necessary. Each part file is no larger than 2.5MB
Adds a powershell script that joins the parts when run, and adds a shortcut file to
run the PowerShell extractor script on double-click, essentially adding a self-extractor
#>
param (
# Path to the file you want to split
[Parameter(Mandatory,HelpMessage='Path to the file you want to split')]
[String]
$Path,
# maximum size of file chunks (in bytes)
[int]
$PartSizeBytes = 1MB,
# when specified, add a an extractor script and link file to easily convert
# chunks back into the original file
[Switch]
$AddSelfExtractor
)
try
{
# get the path parts to construct the individual part
# file names:
$fullBaseName = [IO.Path]::GetFileName($Path)
$baseName = [IO.Path]::GetFileNameWithoutExtension($Path)
$parentFolder = [IO.Path]::GetDirectoryName($Path)
$extension = [IO.Path]::GetExtension($Path)
# get the original file size and calculate the
# number of required parts:
$originalFile = New-Object -TypeName System.IO.FileInfo `
-ArgumentList ($Path)
$totalChunks = [int]($originalFile.Length / $PartSizeBytes) + 1
$digitCount = [int][Math]::Log10($totalChunks) + 1
# read the original file and split into chunks:
$reader = [IO.File]::OpenRead($Path)
$count = 0
$buffer = New-Object -TypeName Byte[] -ArgumentList $PartSizeBytes
$moreData = $true
# read chunks until there is no more data
while($moreData)
{
# read a chunk
$bytesRead = $reader.Read($buffer, 0, $buffer.Length)
# create the filename for the chunk file
$chunkFileName = "$parentFolder\$fullBaseName.{0:D$digitCount}.part" -f $count
Write-Verbose -Message "saving to $chunkFileName..."
$output = $buffer
# did we read less than the expected bytes?
if ($bytesRead -ne $buffer.Length)
{
# yes, so there is no more data
$moreData = $false
# shrink the output array to the number of bytes
# actually read:
$output = New-Object -TypeName Byte[] -ArgumentList $bytesRead
[Array]::Copy($buffer, $output, $bytesRead)
}
# save the read bytes in a new part file
[IO.File]::WriteAllBytes($chunkFileName, $output)
# increment the part counter
++$count
}
# done, close reader
$reader.Close()
# add self-extractor
if ($AddSelfExtractor)
{
Write-Verbose -Message "Adding extractor scripts..."
# define the self-extractor powershell script:
$extractorName = "${fullBaseName}.{0:D$digitCount}.part.ps1" `
-f $count
$extractorPath = Join-Path -Path $parentFolder `
-ChildPath $extractorName
$filePath = '$PSScriptRoot\' + "$baseName$extension"
# define the self-extractor shortcut file that launches
# the powershell script on double-click:
$linkName = "Extract ${fullBaseName}.lnk"
$linkPath = Join-Path -Path $parentFolder -ChildPath $linkName
# this will be used inside the extractor script to find the
# part files via relative path:
$currentFile = '"$PSCommandPath"'
$currentFolder = '"$PSScriptRoot"'
# write the extractor script source code to file:
"
# copy the join-file source code into the extractor script:
function Join-File {
${function:Join-File}
}
# join the part files and delete the part files after joining:
Join-File -Path ""$filePath"" -Verbose -DeletePartFiles
# remove both extractor scripts:
(Join-Path -Path $currentFolder -ChildPath '$linkName') `
| Remove-Item
Remove-Item -Path $currentFile
# open the extracted file in windows explorer
explorer.exe ""/select,""""$filepath""""""
" | Set-Content -Path $extractorPath
# create a shortcut file that launches the extractor script
# when it is double-clicked:
$shell = New-Object -ComObject WScript.Shell
$scut = $shell.CreateShortcut($linkPath)
$scut.TargetPath = "powershell.exe"
$scut.Arguments = "-nop -executionpolicy bypass `
-file ""$extractorPath"""
$scut.WorkingDirectory = ""
$scut.IconLocation = "$env:windir\system32\shell32.dll,162"
$scut.Save()
}
}
catch
{
throw "Unable to split file ${Path}: $_"
}
}
if(!(get-alias splitFileBySize) 2>&1 | out-null) {
new-alias splitFileBySize Split-File
}
Or split by lines
function splitFileByLines() {
Param(
[parameter(Mandatory=$true)]
[string]$file = $null,
[parameter(Mandatory=$true)]
[int]$lines = 0,
[parameter(Mandatory=$true)]
[string]$outdir = $null
)
# check file exists
if($f = get-item $file) {
$basename = $f.basename
$ext = $f.Extension
} else {
write-error "File not found"
break;
}
# check outdir
if(!exists($outdir)) {
mkdir $outdir
}
$i=0;
get-content $f.fullname -readcount $lines -encoding UTF8 `
| % { $i++; $_ | out-file "$($outdir)\$($basename)_part$($i).$(ext)" }
}
Measure network speed
function Measure-NetworkSpeed {
param(
[Parameter(Position=0, mandatory=$false)]
[Alias('size')]
[string]$downloadsize = "100"
)
write-host ""
write-host "Usage: Measure-NetworkSpeed [-size] <100|1000|10000> (default: 100MB) [-proxy]"
write-host ""
switch($downloadsize.toupper()) {
"100M" { $downloadsize = "100"; }
"100MB" { $downloadsize = "100"; }
"1G" { $downloadsize = "1000"; }
"1GB" { $downloadsize = "1000"; }
"10G" { $downloadsize = "10000"; }
"10GB" { $downloadsize = "10000"; }
}
[int]$size = $downloadsize
$URLS = @{ "100"='https://nbg1-speed.hetzner.com/100MB.bin';
"1000"='https://nbg1-speed.hetzner.com/1GB.bin';
"10000"='https://nbg1-speed.hetzner.com/10GB.bin'
}
$URL = $URLS["$downloadsize"]
# The test file has to be a 10MB file for the math to work.
# If you want to change sizes, modify the math to match
$TestFile = $URL
$TempFile = Join-Path -Path $env:TEMP -ChildPath 'testfile.tmp'
$WebClient = New-Object Net.WebClient
#write-host "WebclientProxy: $($WebClient.proxy)"
$TimeTaken = Measure-Command { $trash = $WebClient.DownloadData($TestFile); } | Select-Object -ExpandProperty TotalSeconds
$SpeedMbps = ($size / $TimeTaken) * 8
$Message = "{0:N2} Mbit/sec" -f ($SpeedMbps)
return $Message
}
if(!(get-alias speedtest) 2>&1 | out-null) {
new-alias speedtest Measure-NetworkSpeed
}