Skip to content

Instantly share code, notes, and snippets.

@AfroThundr3007730
Last active April 7, 2024 22:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AfroThundr3007730/5c1d9213b2fb51a23c951da95aa7dd30 to your computer and use it in GitHub Desktop.
Save AfroThundr3007730/5c1d9213b2fb51a23c951da95aa7dd30 to your computer and use it in GitHub Desktop.
Collection of helpful bash and powershell functions (most are in my other gists).
#!/bin/bash
#Functions pulled from other sources
# Source: https://gist.github.com/flungo/428c374c040de1d0a30fd4a593d39040
function lsiommu() {
for d in $(find /sys/kernel/iommu_groups/ -type l | sort -n -k5 -t/); do
n=${d#*/iommu_groups/*}
n=${n%%/*}
printf 'IOMMU Group %s ' "$n"
lspci -nns "${d##*/}"
done
}
# Source: https://gist.github.com/r15ch13/ba2d738985fce8990a4e9f32d07c6ada
function lsiommu2() {
shopt -s nullglob
lastgroup=""
for g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do
for d in "$g"/devices/*; do
if [ "${g##*/}" != "$lastgroup" ]; then
echo -en "Group ${g##*/}:\t"
else
echo -en "\t\t"
fi
lastgroup=${g##*/}
lspci -nms "${d##*/}" | awk -F'"' '{printf "[%s:%s]", $4, $6}'
if [[ -e "$d"/reset ]]; then echo -en " [R] "; else echo -en " "; fi
lspci -mms "${d##*/}" | awk -F'"' '{printf "%s %-40s %s\n", $1, $2, $6}'
for u in "$d"/usb*/; do
bus=$(cat "${u}/busnum" 2 &>/dev/null)
lsusb -s "$bus": | awk '{
gsub(/:/, "", $4); printf "%s|%s %s %s %s|", $6, $1, $2, $3, $4;
for(i = 7; i <= NF; i++){printf "%s ", $i}; printf "\n"
}' | awk -F'|' '{printf "USB:\t\t[%s]\t\t %-40s %s\n", $1, $2, $3}'
done
done
done
shopt -u nullglob
}
#!/bin/bash
# Last updated 20240406 by AfroThundr
# SPDX-License-Identifier: GPL-3.0-or-later
#set -euo pipefail
#shopt -s extdebug
#trap 's=$?; echo >&2 "$0: Error on line $LINENO: $BASH_COMMAND"; exit $s' ERR
alias gen-challenge="dd if=/dev/urandom count=16 bs=1 status=none | hexdump -e '8/1 \"%02X \" \"\\n\"'"
alias glances='nice -n 19 ionice -c 3 glances'
alias gpg='gpg -v --expert'
alias gpg-minexport='gpg --export-options export-clean,export-minimal,no-export-attributes'
alias htop='nice -n 19 ionice -c 3 htop'
alias myip='echo "My public IP is: $(curl -4 https://icanhazip.com 2>/dev/null)"'
alias myip6='echo "My public IP is: $(curl -6 https://icanhazip.com 2>/dev/null)"'
alias pwgen='pwgen -1sy'
alias pwsh='pwsh -NoLogo'
alias sudo='sudo '
alias ujournalctl='journalctl --user'
alias usystemctl='systemctl --user'
alias wine='flatpak run org.winehq.Wine'
alias winetricks='flatpak run --command=winetricks org.winehq.Wine'
alias xfreerdp='xfreerdp +glyph-cache +toggle-fullscreen /dynamic-resolution '
function itunes_upc() {
curl -s https://itunes.apple.com/lookup?upc="$1" | jq
}
function split_music_compilation() {
[[ -f $1 ]] || {
printf 'Audio file not found: %s' "$1"
return 1
}
[[ -f $2 ]] || {
printf 'Cuesheet file not found: %s' "$2"
return 1
}
local source=$1 cuesheet=$2 count=0 pstamp='0:00' stamp title album
while read -r line; do
[[ $line =~ ([0-9]+:[0-9]+:[0-9]+).-.(.*).\[(.*)\] ]] && {
stamp=${BASH_REMATCH[1]}
title=${BASH_REMATCH[2]}
album=${BASH_REMATCH[3]}
[[ count -gt 0 ]] && nice -n 19 ionice -c 3 ffmpeg -i "$source" -ss "$pstamp" \
-t $(($(date -d "$stamp" +%s) - $(date -d "$pstamp" +%s))) -c copy -v 0 \
"$(printf '%02d - %s [%s].%s' "$count" "$ptitle" "$palbum" "${source##*.}")" &
((count++))
pstamp=$stamp
ptitle=$title
palbum=$album
}
done <"$cuesheet"
nice -n 19 ionice -c 3 ffmpeg -i "$source" -ss "$pstamp" -c copy -v 0 \
"$(printf '%02d - %s [%s].%s' "$count" "$title" "$album" "${source##*.}")"
return 0
}
function getpower() {
printf "%.1f W Battery\n" "$(
bc -l <<<"$(</sys/class/power_supply/BAT0/current_now) * \
$(</sys/class/power_supply/BAT0/voltage_now) / 10^12"
)"
printf "%.1f W Charging\n" "$(
bc -l <<<"$(</sys/class/power_supply/BAT0/charge_now) * \
$(</sys/class/power_supply/BAT0/voltage_now) / 10^12"
)"
}
function genpw() {
local length=20 count=5
while [[ -n $1 ]]; do
[[ $1 =~ -l|--length ]] && {
length=$2
shift 2
}
[[ $1 =~ -c|--count ]] && {
count=$2
shift 2
}
[[ $1 =~ -q|--quiet ]] && {
local quiet=true
shift
}
done
[[ -n $quiet ]] ||
printf 'Generating %d passwords of length %d\n\n' "$count" "$length"
printf '%b\n' "$(
tr -dc '[:graph:]' </dev/urandom | fold -w "$length" | head -n "$count"
)"
[[ -n $quiet ]] ||
printf '\nEffective entropy of each: %.2f\n' \
"$(bc -l <<<"l(94^$length)/l(2)")"
}
function get_stream_stats() {
awk 'BEGIN {
match(ARGV[1], /([^-\/]+)-([0-9-]+)/, meta)
printf("Channel: %s Date: %s\n", meta[1], meta[2])
} /[Cc]heer/ {
for (i = 1; i <= NF; i++) {
if ($i ~ /^[Cc]heer$/) {
j = i + 1
token = $i $j
} else { token = $i }
match(token, /[Cc]heer([0-9]+)/, bit)
bits += bit[1]
}
} /subscribed/ {
match($0, /subscribed at Tier ([1-3])|subscribed with (Prime)/, tier)
subs[tier[1]]++
} /gifting/ {
match($0, /is gifting ([0-9]+) Tier ([1-3]) Subs to/, gift)
gifts[gift[2]] += gift[1]
} $2 ~ /streamelements:/ && /just tipped/ {
match($0, /just tipped \$([0-9\.]+)/, tip)
tips++
tipi += (tip[1] - .3) * .97
} $2 ~ /soundalerts:/ && /play/ {
match($0, /played .+ for ([0-9]+) Bits|used ([0-9]+) Bits to play/, abit)
alerts++
abits += abit[1] + abit[2]
} END {
printf("\nStream statistics:\n")
printf("%13s %\47-5d\t%14s %\47-8d\n",
"Prime Subs:", subs[""], "Cheered Bits:", bits)
for (i = 1; i <= 3; i++) {
printf("%13s %\47-5d\t%14s %\47-5d\n",
"Tier " i " Subs:", subs[i], "Tier " i " Gifted:", gifts[i])
}
printf("%13s %\47-5d\t%14s %\47-5d\n",
"SE Tips:", tips, "Sound Alerts:", alerts)
biti = bits / 100
subi = (subs[""] * 5 + subs[1] * 5 + subs[2] * 10 + subs[3] * 25) * .5
gifti = (gifts[1] * 5 + gifts[2] * 10 + gifts[3] * 25) * .5
abiti = abits / 100 * .8
printf("\nEstimated stream income:\n")
printf("%15s\t$%\47 10.2f\n", "Bit Cheers:", biti)
printf("%15s\t$%\47 10.2f\n", "Subscriptions:", subi)
printf("%15s\t$%\47 10.2f\n", "Gifted Subs:", gifti)
printf("%15s\t$%\47 10.2f\n", "SE Tips:", tipi)
printf("%15s\t$%\47 10.2f\n", "Sound Alerts:", abiti)
printf("%15s\t$%\47 10.2f\n",
"Total Income:", biti + subi + gifti + tipi + abiti)
printf("\nUsing rates:\n")
printf("\tBits: %.2f, Subs: %.2f, Tips: %.2f, Alerts: %.2f\n",
1.0, 0.5, 0.97, 0.8)
}' "$1"
}
function streamlink_vod() {
opts=(
--player-passthrough http
--stream-segment-timeout 1
--stream-segment-threads 2
)
[[ -n $2 ]] && opts+=(--hls-start-offset "$2")
~/.local/bin/streamlink "${opts[@]}" "$1" best
}
function streamlink_save {
file=${1##*/}
file=${file%%\?*}
streamlink -o "${2:-$file}".m2t "$1" best
}
function streamlink_audio {
file=${1##*/}
file=${file%%\?*}
streamlink --stdout "$1" best | ffmpeg -i pipe:0 "$file".aac
}
function kill_ffcp() {
ps auxk -%mem | awk '/firefox/ && /contentproc/ && $4 >= 1 {print $2}' |
head -n "${1:-1}" | xargs -l kill -9
}
# shellcheck source=contrib.sh
. "$(dirname "${BASH_SOURCE[0]}")"/contrib.sh
# Last updated 20240331 by AfroThundr
# SPDX-License-Identifier: GPL-3.0-or-later
Set-StrictMode -Version Latest
#region Internal Variables
$DefaultPSProfileContent = @'
# Note: This profile stub is automatically overwritten, make changes instead in profile_local.ps1
# Always use strict mode
Set-StrictMode -Version Latest
# Profile and module auto-update
function _update {
$remote = '\\file-share\Scripts\modules\HelperFunctions.psm1'
$local = $env:PSModulePath.split(';')[0] + '\HelperFunctions\HelperFunctions.psm1'
# Update the module if there are changes
if (([IO.Fileinfo]$remote).Exists) {
Try {
if (!(Get-Command -Name Update-InternalModule -ErrorAction SilentlyContinue)) {
New-Module -Name HelperFunctionsTemp -ScriptBlock (
[scriptblock]::Create([IO.File]::ReadAllText($remote))
) | Import-Module
}
$updated = Update-InternalModule -Remote $remote -Local $local
Remove-Module -Name HelperFunctionsTemp -ErrorAction Ignore
} Catch { Write-Output 'Failed to update module HelperFunctions' }
}
# Import the module and update this profile stub
Try {
Import-Module -Name HelperFunctions -Force
if ($updated) { Initialize-PSProfileAutoUpdate -Overwrite }
} Catch { Write-Output 'Unable to import module HelperFunctions' }
# Import the local profile (if it exists)
$localProfile = $PROFILE.CurrentUserAllHosts.ForEach{ $_.Insert($_.LastIndexOf('.'), '_local') }
if (([IO.FIleInfo][string]$localProfile).Exists) { . $localProfile }
}
_update
'@
#endregion
#region External Functions
#region Introspection
function Get-StaticProperties {
<# .SYNOPSIS
Enumerates the static properties of a class #>
Param(
# The class to enumerate
[Parameter(Mandatory)]
[type]$Class
)
return (Get-Member -InputObject $Class -Static -MemberType Property).ForEach{
@{ $_.Name = $Class::($_.Name) }
}
}
function Get-StaticMethods {
<# .SYNOPSIS
Enumerates the static methods of a class #>
Param(
# The class to enumerate
[Parameter(Mandatory)]
[type]$Class
)
return (Get-Member -InputObject $Class -Static -MemberType Method).ForEach{
[PSCustomObject]@{ Name = $_.Name; OverloadDefinitions = $class::($_.Name) }
}
}
function Get-TypeAccelerators {
<# .SYNOPSIS
Enumerate the available type accelerators #>
Param(
# Filter by type FullName
[string]$Filter
)
return [PSObject].Assembly.GetType(
'System.Management.Automation.TypeAccelerators'
)::get.GetEnumerator().Where({ $_.Value.Fullname -match $Filter })
}
function Get-ExportedTypes {
<# .SYNOPSIS
Get a list of exported types matching the provided filter #>
Param(
# Filter to match against
[string]$Filter,
# Match against the type FullName
[switch]$FullName
)
return [AppDomain]::CurrentDomain.GetAssemblies().GetExportedTypes().Where{
$_.IsPublic -and !$_.IsGenericType
}.Where{
if ($FullName) { $_.FullName -match $Filter } else { $_.Name -match $Filter }
}.FullName | Sort-Object
}
#endregion
#region Security
function Set-ScriptSignature {
<# .SYNOPSIS
Wrapper function to sign and timestamp a script file #>
[Alias('SignScript')]
Param(
# The script file to sign
[Parameter(Mandatory)]
[IO.FileInfo]$ScriptFile
)
Set-AuthenticodeSignature -FilePath $ScriptFile -Certificate (
Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert
)[0] -TimestampServer 'http://timestamp.digicert.com' #DevSkim: ignore DS137138
}
function New-SecurePassword {
<# .SYNOPSIS
Generates high entropy passwords of configurable length #>
[Alias('genpw')]
Param(
# Length of passwords to genereate
[int]$Length = 20,
# How many passwords to generate
[int]$Count = 5,
# Quiet mode, only emit passwords
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length)
}
foreach ($_ in (1..$Count)) {
$b = [byte[]]::new(1)
$gen = [Security.Cryptography.RandomNumberGenerator]::Create()
[string]::Concat((1..$Length | & { process {
do { $gen.GetBytes($b) } until ($b -ge 33 -and $b -le 126); [char[]]$b
} }))
}
if (!$Quiet) {
Write-Output ("`nEffective entropy of each: {0:n}" -f
([math]::log([math]::pow(94, $Length)) / [math]::log(2)))
}
}
if ($PSVersionTable.PSVersion.Major -ge 7) {
function New-SecurePassword {
<# .SYNOPSIS
Generates high entropy passwords of configurable length #>
[Alias('genpw')]
Param(
# Length of passwords to genereate
[int]$Length = 20,
# How many passwords to generate
[int]$Count = 5,
# Quiet mode, only emit passwords
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} passwords of length {1}`n" -f $Count, $Length)
}
foreach ($_ in (1..$Count)) {
[string]::Concat([char[]](Get-Random -Min 33 -Max 126 -Count $Length))
}
if (!$Quiet) {
Write-Output ("`nEffective entropy of each: {0:n}" -f
[math]::log2([math]::pow(94, $Length)))
}
}
}
function New-SecureKey {
<# .SYNOPSIS
Generates high entropy keys of configurable size #>
[Alias('genkey')]
Param(
# Length of keys to genereate
[int]$Bytes = 16,
# How many keys to generate
[int]$Count = 1,
# Quiet mode, only emit keys
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} keys of size {1}`n" -f $Count, $Bytes)
}
foreach ($_ in (1..$Count)) {
$buff = [byte[]]::new($Bytes)
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($buff)
[string]::Concat(($buff | & { process { $_.ToString('x2') } }))
}
if (!$Quiet) {
Write-Output ("`nEntropy of each: {0}" -f ($Bytes * 8))
}
}
if ($PSVersionTable.PSVersion.Major -ge 7) {
function New-SecureKey {
<# .SYNOPSIS
Generates high entropy keys of configurable size #>
[Alias('genkey')]
Param(
# Length of keys to genereate
[int]$Bytes = 16,
# How many keys to generate
[int]$Count = 1,
# Quiet mode, only emit keys
[switch]$Quiet
)
if (!$Quiet) {
Write-Output ("Generating {0} keys of size {1}`n" -f $Count, $Bytes)
}
foreach ($_ in (1..$Count)) {
[String]::Concat(
([Security.Cryptography.RandomNumberGenerator]::GetBytes($Bytes) |
& { process { $_.ToString('x2') } }))
}
if (!$Quiet) {
Write-Output ("`nEntropy of each: {0}" -f ($Bytes * 8))
}
}
}
function Get-SSLServerCertificate {
<# .SYNOPSIS
Retrieves the X509 certificate by connecting to a SSL enabled server #>
[Alias('s_client')]
Param(
# Hostname or IP address to connect to
[Parameter(Mandatory)]
[String]$Hostname,
# Port to connect to
[Parameter()]
[Int]$Port = 443,
# Save cert to file
[Parameter()]
[switch]$Save,
# Don't output base64
[Parameter()]
[switch]$AsBase64
)
$sslStream = [Net.Security.SslStream]::new(
[Net.Sockets.TcpClient]::new($Hostname, $Port).GetStream(), $false, { $true })
$sslStream.AuthenticateAsClient((
& { if ($env:COMPUTERNAME) { $env:COMPUTERNAME } else { $env:HOSTNAME } }))
if ($Save) {
[IO.File]::WriteAllBytes(
[IO.Path]::Combine($pwd.Path, (
($sslStream.RemoteCertificate.Subject.replace('*', '_') -split ',|=')[1] + '.cer')),
$sslStream.RemoteCertificate.GetRawCertData())
}
if ($AsBase64) { $sslStream.RemoteCertificate.ExportCertificatePem() }
else { $sslStream.RemoteCertificate }
$sslStream.Dispose()
}
function Get-CertificateKeyData {
<# .SYNOPSIS
Retrieves the private key for a certificate object #>
Param(
# The certificate to retrieve the private key from
[Parameter(Mandatory, ValueFromPipeline)]
[X509Certificate]$Certificate
)
switch -Regex ($Certificate.SignatureAlgorithm.FriendlyName) {
'^.+RSA$' {
return [Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey(
$Certificate).ExportRSAPrivateKeyPem()
}
'.+ECDSA$' {
return [Security.Cryptography.X509Certificates.ECDSACertificateExtensions]::GetECDSAPrivateKey(
$Certificate).ExportECPrivateKeyPem()
}
'.+DSA$' {
return [Security.Cryptography.X509Certificates.DSACertificateExtensions]::GetDSAPrivateKey(
$Certificate).ExportPkcs8PrivateKeyPem()
}
default { Write-Error ('Unsupported certificate key type: {0}' -f $_) }
}
}
#endregion Security
#region Utilities
function Get-StringPermutations {
<# .SYNOPSIS
Calculates the permutations of an input string #>
[Alias('permutate')]
Param(
# String to calculate permutations from
[Parameter(Mandatory)]
[String]$String,
# Return only unique permutations
[Parameter()]
[Switch]$Unique
)
function _Permutate($String, $Stub = '') {
if ($String.length -eq 2) {
return ($Stub + $String[0] + $String[1]), ($Stub + $String[1] + $String[0])
}
for ($i = 0; $i -lt $String.Length; $i++) {
_Permutate -String $String.remove($i, 1) -Stub ($Stub + $String[$i])
}
}
if ($String.length -eq 1) { return $String }
if (!$Unique) { _Permutate -String $String } else {
[Collections.Generic.HashSet[string]]::new([string[]](_Permutate -String $String))
}
}
function Get-Factorial {
<# .SYNOPSIS
Get factorial of a number #>
[Alias('factorial', 'x!')]
Param(
# Number to compute factorial for
[Parameter(Mandatory, ValueFromPipeline)]
[double]$Value
)
if ($Value -ge 1) {
return $Value * (Get-Factorial -Value ($Value - 1))
} elseif ($Value -eq 0) {
return 1
} else {
Throw 'NaN'
}
}
function Get-GreatestCommonDenominator {
<# .SYNOPSIS
Find the greatest common denominator of two integers #>
[Alias('Get-GCD', 'gcd')]
param(
[Parameter(Mandatory)]
[double]$ValueA,
[Parameter(Mandatory)]
[double]$ValueB
)
Write-Verbose ('a: {0:d}, b: {1:d}' -f $ValueA, $ValueB)
if ($ValueB -eq 0) {
return $ValueA
} else {
return Get-GreatestCommonDenominator -ValueA $ValueB -ValueB ($ValueA % $ValueB)
}
}
function Get-Base64String {
<# .SYNOPSIS
Calculates the base64 of a string or a file #>
[Alias('base64')]
Param(
# Type of object (file or string)
[Parameter()]
[ValidateSet('File', 'String')]
[String]$Type = 'String',
# What to get the base64 from
[Parameter(Position = 0, ValueFromPipeline)]
[String]$InputObject
)
if ($Type -ieq 'File') {
return [Convert]::ToBase64String([IO.File]::ReadAllBytes($InputObject))
} elseif ($Type -ieq 'String') {
return [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($InputObject))
} else {
Throw 'Invalid Type Specified'
}
}
function Get-StringHash {
<# .SYNOPSIS
Calculates the hash of a string using the specified algorithm #>
[Alias('hash')]
Param (
# The string to get a hash for
[Parameter(Position = 0, ValueFromPipeline)]
[String]$InputString,
# The hashing algorithm to use
[Parameter(Position = 1)]
[String]$Algorithm = 'SHA256'
)
return [string]::Concat(
([Security.Cryptography.HashAlgorithm]::Create($Algorithm).ComputeHash(
[Text.Encoding]::UTF8.GetBytes($InputString)) | & { process { $_.ToString('x2') } }))
}
function Measure-CommandBatch {
<# .SYNOPSIS
Wrapper for Measure-Command to get batch execution stats #>
[Alias('BatchMeasure')]
Param(
# Command to measure
[Parameter(Mandatory, ValueFromPipeline)]
[ScriptBlock]$Expression,
# Number of executions to measure
[Parameter()]
[int]$Count = 5,
# Specify a property to measure
[Parameter()]
[String]$Property = 'TotalMilliseconds'
)
if ($PSVersionTable.PSVersion.Major -ge 6) {
return (1..$Count) | & { process { Measure-Command -Expression $Expression } } |
Measure-Object -AllStats -Property $Property
} else {
return (1..$Count) | & { process { Measure-Command -Expression $Expression } } |
Measure-Object -Sum -Average -Maximum -Minimum -Property $Property
}
}
function Hide-ConsoleWindow() {
<# .SYNOPSIS
Hides a console window completely #>
Param()
Add-Type -Name User32 -Namespace Win32 -MemberDefinition `
'[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$Host.UI.RawUI.WindowTitle = [Guid]::NewGuid()
[Win32.User32]::ShowWindowAsync(
(Get-Process).where{ $_.MainWindowTitle -eq $Host.UI.RawUI.WindowTitle }.MainWindowHandle, 0)
}
#endregion
#region Directory Services
function Get-SMSManagementPoint {
<# .SYNOPSIS
Finds SCCM management point based on site code #>
Param(
# The site code to search
[Parameter(Mandatory)]
[string]$SiteCode
)
return [adsisearcher]::new(
[adsi]('LDAP://CN=Partitions,' + (
[adsi]'LDAP://RootDSE').get('configurationNamingContext')
),
'(&(objectCategory=crossref)(netbiosname=*))'
).findAll().Properties.ncname.forEach{
Try {
[adsisearcher]::new(
[adsi]('LDAP://CN=System Management,CN=System,' + $_),
"(&(ObjectClass=mSSMSManagementPoint)(Name=SMS-MP*-$SiteCode-*))"
).findAll().Properties.mssmsmpname
} Catch {}
}
}
function Resolve-DnsRecordAllDCs {
<# .SYNOPSIS
Resolves a DNS record on all active DCs in a domain #>
[Alias('nslookup_all')]
Param(
# Name to resolve
[Parameter(Mandatory, ValueFromPipeline)]
[string]$Name,
# Type of record
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Type = 'A',
# Batch size for parallel query
[Parameter()]
[int]$ThrottleLimit = 10
)
Resolve-DnsName -Type 'SRV' -Name _ldap._tcp.dc._msdcs.$env:USERDNSDOMAIN |
Where-Object { $_.Type -eq 'SRV' } | ForEach-Object -ThrottleLimit $ThrottleLimit -Parallel {
if (Test-Connection -TargetName $_.NameTarget -Count 1 -TimeoutSeconds 1 -Quiet) {
Resolve-DnsName -Type $Using:Type -Name $Using:Name -Server $_.NameTarget `
-DnsOnly -QuickTimeout -ErrorAction SilentlyContinue
}
} | Where-Object { $_.QueryType -eq $Type } # | Select-Object -Unique
}
function Get-ADComputerReport {
<# .SYNOPSIS
Generate a CSV report on AD computer objects. #>
Param(
# Site code to search
[string]$Site = 'AYZZ',
# Output file
[IO.FileInfo]$File = '.\computers.csv'
)
Get-ADComputer -Filter ('Name -like "*{0}*"' -f $Site) -Property * | Sort-Object |
Select-Object -Property Name,
@{ N = 'DNSHostName'; E = { if ($_.DNSHostName) { $_.DNSHostname } else { 'Unknown' } } },
@{ N = 'IPv4Address'; E = { if ($_.IPv4Address) { $_.IPv4Address } else { 'Unknown' } } },
Enabled,
@{ N = 'Modified'; E = { if ($_.Modified) { $_.Modified } else { 'Unknown' } } },
@{ N = 'PasswordLastSet'; E = { if ($_.PasswordLastSet) { $_.PasswordLastSet } else { 'Unknown' } } },
@{ N = 'LastLogonDate'; E = { if ($_.LastLogonDate) { $_.LastLogonDate } else { 'Unknown' } } },
@{ N = 'OSVersion'; E = { if ($_.OperatingSystemVersion) {
switch -wildcard ($_.OperatingSystemVersion) {
'*14393*' { $_ + ' [1607]' } '*17763*' { $_ + ' [1809]' }
'*18363*' { $_ + ' [1909]' } '*19042*' { $_ + ' [20H2]' }
'*19044*' { $_ + ' [21H2]' } '*19045*' { $_ + ' [22H2]' }
'*22000*' { $_ + ' [21H2]' } '*22621*' { $_ + ' [22H2]' }
'*22631*' { $_ + ' [23H2]' } default { $_ + ' [Unknown]' }
}
} else { 'Unknown' } }
},
Description,
@{ N = 'MemberOf'; E = { ($_.MemberOf.ForEach{ ($_ -split '=|,')[1] }) -join ', ' } } |
Export-Csv -Path $File
}
#endregion
#region Miscellaneous
function Write-ScriptEvent {
<# .SYNOPSIS
Wrapper to write PowerShell transcripts to the event log #>
Param(
# Transcript file to read from
[Parameter(Mandatory)]
[string]$LogFile,
# Event source to apply
[Parameter()]
[string]$Source = 'PSScript Transcript',
# Event ID to apply
[Parameter()]
[int]$EventID = 1000
)
$split = [regex]::Escape('**********************')
$lastRun = ((Get-Content $LogFile -Raw) -split ($split))[-3]
if ($lastRun.Length -gt 32600) {
$lastRun = $lastRun.Substring(0, 32600) + "`n`n****OUTPUT TRUNCATED****"
}
if (!([System.Diagnostics.EventLog]::SourceExists($Source))) {
New-EventLog -LogName 'Application' -Source $Source
}
Write-EventLog -LogName 'Application' -Source $Source `
-EntryType Information -EventId $EventID -Message $lastRun
}
function Edit-VMDKHeader {
<# .SYNOPSIS
Edit the metadata header of a VMDK file #>
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')]
Param(
# The VMDK file to edit
[Parameter(Mandatory)]
[ValidateNotNullOrEmpty()]
[IO.FileInfo]$VMDKFile
)
if (!$VMDKFile.Exists) { throw 'The file specified does not exist.' }
$buffer = [byte[]]::new(1024)
$tmpFile = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetTempFileName())
$vmdkStream = [IO.File]::Open($VMDKFile, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite)
$vmdkStream.Position = 512
[void]$vmdkStream.Read($buffer, 0, 1024)
Write-Output 'Dumping VMDK header metadata to:' $tmpFile
$tmpStream = [IO.File]::OpenWrite($tmpFile)
$tmpStream.Write($buffer, 0, 1024)
$tmpStream.Dispose()
Write-Output 'Make necessary changes, then save and close the editor.'
(Start-Process notepad.exe -ArgumentList $tmpFile -PassThru -Wait).WaitForExit()
$tmpStream = [IO.File]::OpenRead($tmpFile)
[void]$tmpStream.Read($buffer, 0, 1024)
$tmpStream.Dispose()
Remove-Item $tmpFile
if ($PSCmdlet.ShouldProcess($VMDKFile, 'Overwrite VMDK header metadata')) {
Write-Output 'Overwriting VMDK header metadata.'
$vmdkStream.Position = 512
$vmdkStream.Write($buffer, 0, 1024)
}
$vmdkStream.Dispose()
}
function Invoke-GithubFileRequest {
<# .SYNOPSIS
Retrieves a file from a Github repository. #>
[Alias('gh_curl')]
Param(
# URL of file to download
[Parameter(Mandatory)]
[Uri]$URL,
# Output file path
[Parameter()]
[IO.FileInfo]$FilePath = $URL.Segments[-1]
)
function _validate {
Param([Uri]$link)
switch ($link.Host) {
'github.com' {
if ($link.Segments.Count -lt 6 -or $link.Segments[3] -ne 'blob/') { break }
return [Uri]('https://api.github.com/repos/{1}/{2}/contents/{5}?ref={4}' -f
$link.LocalPath.Split('/', 6))
}
'raw.githubusercontent.com' {
if ($link.Segments.Count -lt 5) { break }
return [Uri]('https://api.github.com/repos/{1}/{2}/contents/{4}?ref={3}' -f
$link.LocalPath.Split('/', 5))
}
'api.github.com' {
if ($link.LocalPath -notmatch 'git/blobs|contents') { break }
return $link
}
}
Write-Output 'The provided URL is not a valid Github file URL:' $link.OriginalString
break 2
}
[IO.File]::WriteAllBytes($FilePath, [Convert]::FromBase64String(
(Invoke-RestMethod -Uri (_validate $URL)).Content))
}
function Get-GHRepoList {
<# .SYNOPSIS
Gets a list of repositories of a GitHub user. #>
Param(
# User to enumerate
[string]$User,
# Page size to request
[int]$Size = 100
)
$page = 0
$count = (Invoke-RestMethod -Uri ('https://api.github.com/users/{0:s}' -f $User)).public_repos
do {
$page++
(Invoke-RestMethod -Uri ('https://api.github.com/users/{0:s}/repos?page={1:d}&per_page={2:d}' -f $User, $page, $Size)).html_url
Start-Sleep -Seconds 1
} while ($page * $size -lt $count)
}
function Update-LocalNugetRepository {
<# .SYNOPSIS
Update a local Nuget repo from a remote repo #>
Param(
# Remote repository feed URL
[Uri]$FeedURLBase = 'https://aka.ms/sme-extension-feed',
# Local repository directory
[IO.DirectoryInfo]$Destination = [IO.Path]::Join([Environment]::GetFolderPath('MyDocuments'), 'NuGetLocal'),
# Overwrite local file if it exists
[switch]$Overwrite,
# Number of times to retry a download
[int]$Retries = 2
)
function _DownloadEntries {
Param([Uri]$feedURL)
[xml]$feed = [Net.WebClient]::new().DownloadString($feedUrl)
$entries = $feed.feed.entry.where{ $_.properties.IsLatestVersion.'#text' -eq 'true' }
[int]$progress = 0
foreach ($entry in $entries) {
$fileName = $entry.properties.id + '.' + $entry.properties.version + '.nupkg'
[IO.FileInfo]$filePath = [IO.Path]::Join($Destination, $fileName)
[int]$pagePercent = (++$progress) / $entries.Count * 100
if (!$Overwrite -and $filePath.Exists) {
Write-Progress -Activity ('{0} already downloaded' -f $fileName) `
-Status ('{0}% of current page complete' -f $pagePercent) `
-PercentComplete $pagePercent
continue
}
Write-Progress -Activity ('Downloading {0}' -f $fileName) `
-Status ('{0}% of current page complete' -f $pagePercent) `
-PercentComplete $pagePercent
[int]$tries = 0
while ($tries -le $Retries) {
try {
$tries++
[Net.WebClient]::new().DownloadFile($entry.content.src, $filePath)
break
} catch [System.Net.WebException] {
Write-Host ("Problem downloading URL: {0}`tTry: {1}`n`tException: {2}" -f
$entry.content.src, $tries, $_.Exception.Message)
}
}
}
$nextLink = ([object[]]$feed.feed.link).Where{ $_.rel.startsWith('next') }
if ($nextLink) { return [Uri]$nextLink.href }
}
$downloadURL = ([xml][Net.WebClient]::new().DownloadString($FeedURLBase)).service.GetAttribute('xml:base') + '/Packages'
if (!$Destination.Exists) { [void]$Destination.Create() }
while ($downloadURL) { $downloadURL = _DownloadEntries -feedURL $downloadURL }
}
if ($PSVersionTable.PSVersion.Major -ge 7) {
function Install-PythonRelease {
<# .SYNOPSIS
Install the Python language interpreter #>
param(
# Install for all users (requires admin)
[Switch]$AllUsers,
# Use beta build instead of latest stable
[Switch]$Beta,
# Use version X.Y.Z instead of latest stable
[Version]$Version
)
if ($AllUsers -and !(
[Security.Principal.WindowsIdentity]::GetCurrent().groups -contains 'S-1-5-32-544')) {
throw 'The AllUsers switch requires administrator privileges.'
}
[Uri]$baseURI = 'https://www.python.org/ftp/python/'
if (!$Version) {
[Version[]]$versionList = (
Invoke-WebRequest -NoProxy -Uri $baseURI
).links.href.where{ $_ -match '\d+\.\d+\.\d+' }.trim('/')
[Array]::sort($versionList)
$Version = $versionList[$($Beta ? -1 : -2)]
}
[IO.DirectoryInfo]$installDir = $AllUsers ?
[IO.Path]::Join(
[Environment]::GetFolderPath('ProgramFiles'), 'Python',
('Python {0:d}.{1:d}' -f $Version.Major, $Version.Minor)) :
[IO.Path]::Join(
[Environment]::GetFolderPath('LocalApplicationData'), 'Programs', 'Python',
('Python{0:d}{1:d}' -f $Version.Major, $Version.Minor))
if ($installDir.Exists) {
throw 'Selected python release already exists at {0:s}' -f $installDir
}
[Uri]$download = $Beta ?
'{0:s}{1:s}/{2:s}' -f $baseURI, $Version, (
Invoke-WebRequest -NoProxy -Uri ('{0:s}{1:s}' -f $baseURI, $Version)
).links.href.where{ $_ -match 'amd64\.exe$' }[-1] :
'{0:s}{1:s}/python-{1:s}-amd64.exe' -f $baseURI, $Version
$setup = [IO.Path]::Join([IO.Path]::GetTempPath(), $download.Segments[-1])
Invoke-WebRequest -NoProxy -Uri $download -OutFile $setup
if (!([IO.FileInfo]$setup).Exists) {
throw 'Setup file {0:s} not downloaded' -f $setup
}
& $setup /quiet InstallAllUsers=$($AllUsers ? 1 : 0) PrependPath=1 Include_launcher=0 Shortcuts=0
([IO.FileInfo]$setup).Delete()
}
}
function Install-GitSCMRelease {
<# .SYNOPSIS
Installs the Git SCM #>
param()
[Uri]$gitRelease = 'https://api.github.com/repos/git-for-windows/git/releases/latest'
[Uri]$installer = (
Invoke-RestMethod -NoProxy -Uri $gitRelease
).assets.browser_download_url.where{ $_ -match '64-bit.7z.exe' }[0]
[IO.DirectoryInfo]$installDir = [IO.Path]::Join(
[Environment]::GetFolderPath('LocalApplicationData'), 'Programs', 'PortableGit'
)
if ($installDir.Exists) {
throw 'Git already exists at {0:s}' -f $installDir
}
$setup = [IO.Path]::Join([IO.Path]::GetTempPath(), $installer.Segments[-1])
Invoke-WebRequest -NoProxy -Uri $installer -OutFile $setup
if (!([IO.FileInfo]$setup).Exists) {
throw 'Setup file {0:s} not downloaded' -f $setup
}
& $setup -o $installDir -y
[Environment]::SetEnvironmentVariable('Path',
[Environment]::GetEnvironmentVariable('Path', 'User').Insert(
0, [IO.Path]::Join($installDir, 'bin') + ';'), 'User')
([IO.FileInfo]$setup).Delete()
}
function Get-UserSessionsFromSubnet {
<# .SYNOPSIS
Queries computers in a subnet for active logon sessions #>
Param(
# DHCP scope to query for leases
[IPAddress]$ScopeId,
# DHCP server to query
[string]$Server
)
(Get-DhcpServerv4Lease -ComputerName $Server -ScopeId $ScopeId).HostName.ForEach{
Try {
[PSCustomObject]@{
Computer = $_
Sessions = [Collections.Generic.Hashset[String]](
Get-CimInstance -ClassName Win32_LoggedOnUser -ComputerName $_
).Antecedent.Where{ $_.Domain -eq 'AREA52' }.Name.Where{ $_ -notmatch $env:USERNAME }
}
} Catch {} } | Format-Table Name, Value
}
function Get-QUserEntries() {
<# .SYNOPSIS
Get the quser logged on user table #>
Param(
# Computer to query
[string]$Computer = $env:COMPUTERNAME
)
((query user /server:$Computer | Select-Object -Skip 1) -replace '\s{2,}', ',').ForEach{
line = if ($_.Split(',').Count -eq 5) {
$_.Insert($_.IndexOf(','), ',')
} else { $_ }
$user, $session, $id, $state, $idleTime, $logonTime = $line.split(',')
$idleTime = if ($idleTime -match '\+') {
$idleTime.replace('+', '.')
} elseif ($idleTime -match '\d') {
'0:' + $idleTime
} else { 0 }
[PSCustomObject]@{
PSTypeName = 'QuserObject'
UserName = $user.replace('>', '').trim()
Session = $session
ID = [int]$id
State = $state
IdleTime = [timespan]$idleTime
LogonTime = [datetime]$logonTime
IsCurrent = $user.StartsWith('>')
}
}
}
function Stop-InactiveUserSession() {
<# .SYNOPSIS
Removes inactive user sessions on a computer #>
Praram(
# Idle threshold in days
[int]$IdleDays = 2
)
(Get-QUserEntries).Where{
$_.State -match 'Disc' -and $_.IdleTime.Days -ge $idleDays }.ForEach{
Write-Output 'Logging off inactive user' $_.UserName
logoff $_.ID
}
}
#endregion
#region PowerCLI
# Get completion percentage for batch VM clone tasks
function Get-VMDeployProgress() {
[Alias('deployProgress')]
Param(
[string]$VCUser = $global:DefaultVIServers[0].User.Split('\')[1]
)
do {
$Tasks = (Get-Task).Where{ $_.Name -match 'clone' `
-and $_.State -match 'running' `
-and $_.ExtensionData.Info.Reason.UserName -match $VCUser }
if ($null -eq $Original) { $Original = $Tasks.Count }
$Running = $Percent = 0
$Tasks.ForEach{
$Percent += $_.PercentComplete
if ($_.state -match 'Running') { $Running++ }
}
$Total = (($Original - $Running) * 100 + $Percent) / $Original
Write-Progress -Activity 'Deploying VMs' -PercentComplete $total `
-Status ('VMs still deploying: {0} of {1}, {2:n}% complete...' `
-f $running, $original, $total)
Start-Sleep 1
}
while ($Running -ne 0)
}
function Get-RunningTasks() {
<# .SYNOPSIS
Get a running list of active VITasks #>
Param()
while ((Get-Task).Where{ $_.state -eq 'running' }.count -gt 0) {
(Get-Task).Where{ $_.state -eq 'running' } |
Sort-Object name, percentcomplete |
Format-Table Name, State, PercentComplete, StartTime,
@{ L = 'Target'; E = { $_.ExtensionData.Info.EntityName } },
@{ L = 'Initiator'; E = { $_.ExtensionData.Info.Reason.UserName } }
Start-Sleep 10
}
}
function Connect-VIServerAuto () {
<# .SYNOPSIS
Automated PowerCLI connections to vCenter Server #>
Param(
# VCenter server URL to connect to
[string]$server = 'vcenter'
)
if ([System.Security.Principal.WindowsIdentity]::GetCurrent().Groups.Translate(
[System.Security.Principal.NTAccount]) -contains 'DOMAIN\vCenter_Admins') {
[void](Connect-VIServer (Resolve-DnsName $server).Name)
} else {
[IO.FileInfo]$filePath = [IO.Path]::Join($env:APPDATA,
(Get-StringHash "$env:USERNAME,$env:COMPUTERNAME"))
if (!$filePath.Exists) {
$cred = Get-Credential -Message 'Enter credentials for VMWare Admin:'
$cred.UserName, ($cred.Password | ConvertFrom-SecureString) -join ',' |
Out-File $filePath
}
if (!$cred) {
$user, $pass = (Get-Content $filePath).Split(',')
$cred = [System.Management.Automation.PSCredential]::new(
$user, (ConvertTo-SecureString $pass)
)
}
[void](Connect-VIServer (Resolve-DnsName $server).Name -Credential $cred)
$filePath, $user, $pass, $cred = $null
}
}
function Get-ESXiSerials {
<# .SYNOPSIS
Get the serial number and service tags for ESXi hosts #>
Param(
# Full name of host or a regex
[string]$HostSpec
)
Get-View -ViewType HostSystem -Property Name, Hardware.SystemInfo `
-Filter @{ 'Name' = $(if ($HostSpec) { $HostSpec } else { '' }) } |
Sort-Object -Property Name | Select-Object Name,
@{
Name = 'Serials'
Expression = { $_.Hardware.SystemInfo | Select-Object `
@{
Name = 'AssetTag'
Expression = { $_.OtherIdentifyingInfo[0].IdentifierValue }
},
@{
Name = 'ServiceTag'
Expression = { $_.OtherIdentifyingInfo[1].IdentifierValue }
},
@{
Name = 'EnclosureSerialNumberTag'
Expression = { $_.OtherIdentifyingInfo[2].IdentifierValue }
},
@{
Name = 'SerialNumberTag'
Expression = { $_.OtherIdentifyingInfo[3].IdentifierValue }
}
}
} | Select-Object Name -ExpandProperty Serials | Format-Table
}
#endregion
#region Module Setup
function Update-InternalModule {
Param(
# Remote module path
[Parameter(Mandatory)]
[IO.FIleInfo]$Remote,
# Local module path
[Parameter(Mandatory)]
[IO.FileInfo]$Local,
# Don't error if unable to update
[Parameter()]
[switch]$Quiet
)
if (!$Remote.Exists -and !$Quiet) {
Throw 'Unable to check for updates to module, ensure the file is reachable: ' + $Remote.FullName
}
if ($Remote.Exists -and !$Local.Exists -or (Get-FileHash $Remote).Hash -ne (Get-FileHash $Local).Hash) {
[void][IO.Directory]::CreateDirectory($Local.Directory)
[void][IO.File]::Copy($Remote.FullName, $Local.FullName, 1)
return $true
}
return $false
}
function Initialize-PSProfileAutoUpdate {
<# .SYNOPSIS
Creates the user's PowerShell profile and enables module auto updating #>
[Alias('init_profile')]
Param(
# Overwrite the file without asking
[switch]$Overwrite,
# The path to the user's PowerShell profile
[IO.FileInfo]$ProfilePath = $PROFILE.CurrentUserAllHosts
)
if ($ProfilePath.Exists -and !$Overwrite) {
Write-Output 'File exists, use -Overwrite to nuke it'
return
}
[void][IO.Directory]::CreateDirectory($ProfilePath.Directory)
[void][IO.File]::WriteAllText($ProfilePath.FullName, $script:DefaultPSProfileContent)
}
#endregion
#endregion
Export-ModuleMember -Function * -Alias *
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment