Created
February 15, 2026 18:31
-
-
Save milnak/a0d60cab81ee3ac0aa1aeb2e6cc33089 to your computer and use it in GitHub Desktop.
My PowerShell $PROFILE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Download best quality video and audio into default container. | |
| .NOTES | |
| yt-dlp now supports preset aliases: '-f mp3', '-f aac', '-t mp4', '-t mkv' | |
| #> | |
| function Invoke-YtDlp { | |
| param( | |
| [Parameter(Mandatory = $true, ParameterSetName = 'Audio')] | |
| [switch]$Audio, | |
| [Parameter(Mandatory = $true, ParameterSetName = 'Video')] | |
| [switch]$Video, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Uri, | |
| # Audio parameters | |
| [Parameter(Mandatory = $true, ParameterSetName = 'Audio')] | |
| [ValidateSet('best', 'aac', 'alac', 'flac', 'm4a', 'mp3', 'opus', 'vorbis', 'wav')] | |
| [string]$Format | |
| ) | |
| # If the Uri is a Facebook redirect, then parse the redirect query string to get the actual URL | |
| if (([Uri]$Uri).Host -like '*.facebook.com') { | |
| $Uri = [Web.HttpUtility]::ParseQueryString([Uri]([Web.HttpUtility]::UrlDecode($Uri)))[0] | |
| Write-Warning "Facebook Uri redirects to: $Uri" | |
| } | |
| # Common args | |
| $ytdlp_args = ` | |
| '--windows-filenames', ` | |
| '--ignore-config', ` | |
| '--progress', ` | |
| '--no-simulate', ` | |
| # '--progress-template', '"download:[download] %(progress.downloaded_bytes)s/%(progress.total_bytes)s ETA:%(progress.eta)s"', ` | |
| '--output-na-placeholder', 'NA', | |
| '--no-playlist' | |
| if ($Audio) { | |
| $ytdlp_args += ` | |
| '--extract-audio', ` | |
| '--audio-format', $Format | |
| } | |
| elseif ($Video) { | |
| # '--format', 'bestvideo[ext=mp4][vcodec^=avc]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio/best[ext=mp4]/best' | |
| $ytdlp_args += ` | |
| '--format', '"bv*+ba"', '--embed-metadata' | |
| } | |
| # Cookies (if file exists) | |
| $cookieFile = Resolve-Path -LiteralPath '~/cookies.txt' -ErrorAction SilentlyContinue | |
| if ($cookieFile) { | |
| Write-Warning "Using cookies file $cookieFile" | |
| $ytdlp_args += '--cookies', """$cookieFile""" | |
| } | |
| if ($Uri -like '*list=*') { | |
| # Playlist arguments | |
| $response = Read-Host "Playlist detected - are you sure? ('`e[1myes`e[0m' to confirm)" | |
| if ($response -ne 'yes') { | |
| return | |
| } | |
| # To get name of playlist: | |
| # yt-dlp.exe --no-warnings --playlist-start 1 --playlist-end 1 --print '%(channel)s/%(playlist_title)s' $Uri | |
| $ytdlp_args += ` | |
| '--print', '"[%(playlist_index)d/%(n_entries+1)d] %(title).200s"', ` | |
| '--output', '"%(channel)s/%(playlist_title)s/%(title).200s [%(id)s].%(ext)s"' | |
| } | |
| else { | |
| # Single file download arguments | |
| $ytdlp_args += ` | |
| '--print', '"%(title).200s [%(id)s]"' , ` | |
| '--output', '"%(title).200s [%(id)s].%(ext)s"' | |
| } | |
| $ytdlp_args += """$Uri""" | |
| Write-Verbose ('Arguments: {0}' -f ($ytdlp_args -join ' ')) | |
| Start-Process -FilePath 'yt-dlp.exe' -ArgumentList $ytdlp_args -NoNewWindow -Wait | |
| } | |
| <# | |
| .SYNOPSIS | |
| Download file (including torrents) using ARIA | |
| #> | |
| function Invoke-Aria { | |
| param([Parameter(Mandatory)] [string]$Source) | |
| # Can also use "aria2.conf" file. | |
| $ariaArgs = @() | |
| ## Basic Options | |
| $ariaArgs += ` | |
| '--continue=true', ` | |
| '--max-concurrent-downloads=5' | |
| ## HTTP/FTP Options | |
| $ariaArgs += ` | |
| '--max-connection-per-server=4', ` | |
| '--max-tries=50', ` | |
| '--min-split-size=20M', ` | |
| '--retry-wait=30', ` | |
| '--split=4' | |
| ## BitTorrent Specific Options | |
| $ariaArgs += ` | |
| '--enable-dht=true', ` | |
| '--seed-time=0' | |
| ## Advanced Options | |
| $ariaArgs += ` | |
| '--allow-piece-length-change=true', ` | |
| '--console-log-level=warn', ` | |
| '--disk-cache=32M', ` | |
| '--disable-ipv6=true', ` | |
| '--file-allocation=none', ` | |
| '--summary-interval=120' | |
| ## URI/MAGNET/TORRENT_FILE/METALINK_FILE | |
| $ariaArgs += ` | |
| $Source | |
| Start-Process -FilePath 'aria2c.exe' -ArgumentList $ariaArgs -NoNewWindow -Wait | |
| } | |
| <# | |
| .SYNOPSIS | |
| Download binaries by extension from a web page. | |
| #> | |
| function Get-WebPageBinaries { | |
| [CmdletBinding()] | |
| param( | |
| # Uri of page to download from. | |
| [Parameter(Mandatory)] [uri]$Uri, | |
| # Extensions to download | |
| [string[]]$Extensions = @('htm', 'html', 'zip', 'pdf', 'mp3', 'mid'), | |
| # Maximum recursion depth, 0=infinite | |
| [int]$Depth = 1 | |
| ) | |
| wget.exe ` | |
| --verbose ` | |
| --no-parent ` | |
| --recursive ` | |
| --level=$Depth ` | |
| --continue ` | |
| --timestamping ` | |
| --execute robots=off ` | |
| --accept=$($Extensions -join ',') ` | |
| --user-agent='Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/52.0.2725.0 Mobile/13B143 Safari/601.1.46' ` | |
| $Uri | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Creates a temporary folder and cds into it. | |
| Outputs the path to allow for assigning to a variable. | |
| #> | |
| function mdcdtemp { | |
| $tempPath = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName()) | |
| New-Item -Path $tempPath -ItemType Directory -Force | Out-Null | |
| Push-Location -LiteralPath $tempPath | |
| $tempPath | |
| } | |
| <# | |
| .SYNOPSIS | |
| Moves an item to the recycle bin. | |
| .EXAMPLE | |
| Get-Item * |Remove-ItemToRecycleBin -Verbose -Confirm | |
| #> | |
| function Remove-ItemToRecycleBin { | |
| [CmdletBinding(SupportsShouldProcess)] | |
| Param( | |
| [Parameter(Mandatory, ValueFromPipeline)][ValidateNotNullOrEmpty()] | |
| [string[]]$Paths | |
| ) | |
| begin { | |
| $shell = New-Object -ComObject 'Shell.Application' | |
| } | |
| process { | |
| foreach ($path in $Paths) { | |
| if (Test-Path -LiteralPath $path) { | |
| if ($PSCmdlet.ShouldProcess($path, 'Move to Recycle Bin')) { | |
| Write-Verbose "Removing to Recycle Bin: $path" | |
| $item = Get-Item -LiteralPath $path | |
| $directoryPath = Split-Path -Path $item -Parent | |
| $shell.Namespace($directoryPath).ParseName($item.Name).InvokeVerb('delete') | |
| } | |
| } | |
| else { | |
| Write-Warning "Path not found: $path" | |
| } | |
| } | |
| } | |
| end {} | |
| } | |
| <# | |
| .SYNOPSIS | |
| Removes a folder recursively. | |
| .DESCRIPTION | |
| Force removes a folder recursively, prompting first. | |
| #> | |
| function rmrf { | |
| param([Parameter(Mandatory = $true)] [string]$Path) | |
| if (Test-Path -LiteralPath $Path -PathType Container) { | |
| $response = Read-Host "Really remove '$PATH'? ('`e[1myes`e[0m' to confirm)" | |
| if ($response -eq 'yes') { | |
| Remove-Item -Force -Recurse -LiteralPath $Path | |
| } | |
| } | |
| else { | |
| Write-Warning "Path not found or not folder." | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Recursive file find. | |
| .DESCRIPTION | |
| By default will return all files starting in current folder tree. | |
| #> | |
| function rff { | |
| param( | |
| [Parameter(Position = 0)] [string]$Filter = '*', | |
| [Parameter(Position = 1)] [string]$Path = '.' | |
| ) | |
| Get-ChildItem -Recurse -File -Filter $Filter -LiteralPath $Path | Select-Object -ExpandProperty FullName | |
| } | |
| <# | |
| .SYNOPSIS | |
| Recursive Grep | |
| .DESCRIPTION | |
| By default will start in current folder | |
| #> | |
| function rgrep { | |
| param( | |
| [Parameter(Position = 0, Mandatory = $true)] [string]$Pattern, | |
| [Parameter(Position = 1)] [string]$Files = '.' | |
| ) | |
| Get-ChildItem -Recurse -File -Filter $Files | Select-String -Pattern $Pattern | |
| } | |
| <# | |
| .SYNOPSIS | |
| Similar to Get-FileHash but also returns Base64 encoded value in 'HashBase64' field. | |
| #> | |
| function Get-FileHashBase64 { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]]$Path, | |
| [ValidateSet('SHA1', 'SHA256', 'SHA384', 'SHA512', 'MD5', IgnoreCase)] | |
| [string]$Algorithm = 'SHA256' | |
| ) | |
| begin { | |
| switch ($Algorithm) { | |
| 'SHA1' { $algorithmId = 'Security.Cryptography.SHA1Managed' } | |
| 'SHA256' { $algorithmId = 'Security.Cryptography.SHA256Managed' } | |
| 'SHA384' { $algorithmId = 'Security.Cryptography.SHA384Managed' } | |
| 'SHA512' { $algorithmId = 'Security.Cryptography.SHA512Managed' } | |
| 'MD5' { $algorithmId = 'Security.Cryptography.MD5CryptoServiceProvider' } | |
| } | |
| Write-Verbose "Get-FileHashBase64 begin : $algorithmId" | |
| } | |
| process { | |
| foreach ($file in $Path) { | |
| $resolvedFile = Resolve-Path $File | |
| Write-Verbose "Get-FileHashBase64 process $resolvedFile" | |
| $hash = (New-Object -TypeName $algorithmId).ComputeHash([IO.File]::OpenRead($resolvedFile)) | |
| [PSCustomObject]@{ | |
| 'Algorithm' = $Algorithm | |
| 'Hash' = [BitConverter]::ToString($hash) -replace '-', '' | |
| 'HashBase64' = [Convert]::ToBase64String($hash) | |
| 'Path' = $resolvedFile | |
| } | |
| } | |
| } | |
| end { | |
| Write-Verbose 'Get-FileHashBase64 end' | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Use sysinternals "du" to show child folder sizes. | |
| #> | |
| function Invoke-DU { | |
| param ([string]$Path = '.') | |
| du.exe -nobanner -c -l 1 $Path | |
| | ConvertFrom-Csv | |
| | Sort-Object -Descending { [uint64]$_.DirectorySizeOnDisk } | |
| | Select-Object -First 15 @{ Name = 'Size'; Expression = { '{0,15:N0}' -f [uint64]$_.DirectorySizeOnDisk } }, Path | |
| } | |
| <# | |
| .SYNOPSIS | |
| Take ownership of a file or folder. | |
| .NOTES | |
| takeown.exe and icacls.exe (both included in Windows) need to be in $env:PATH | |
| If a folder is specified, all files and subfolders of that folder will change ownership. | |
| .EXAMPLE | |
| Set-SelfOwnership 'd:\temp\v.ps1' | |
| Set-SelfOwnership -Confirm 'd:\temp\v.ps1' | |
| Get-ChildItem 'd:\temp\g*' | Set-SelfOwnership -WhatIf -Verbose | |
| #> | |
| function Set-SelfOwnership { | |
| # Support -Confirm, -WhatIf | |
| [CmdletBinding(SupportsShouldProcess)] | |
| param( | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [Alias('FullName')] | |
| [string[]]$Path | |
| ) | |
| begin { | |
| Write-Verbose 'Set-SelfOwnership begin' | |
| # Ensure admin | |
| If (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { | |
| throw 'Admin privileges required' | |
| } | |
| # Check for required apps. | |
| 'takeown', 'icacls' | ForEach-Object { | |
| Get-Command -Name "$_.exe" -CommandType Application -ErrorAction Stop | Out-Null | |
| } | |
| } | |
| process { | |
| foreach ($item in $Path) { | |
| Write-Verbose "Taking ownership of $item" | |
| $ResolvedPath = Resolve-Path -LiteralPath $Path -ErrorAction Stop | |
| if ($PSCmdlet.ShouldProcess($ResolvedPath, 'Take Ownership')) { | |
| if (Test-Path -LiteralPath $ResolvedPath -PathType Container) { | |
| takeown.exe /f $ResolvedPath /r /d y | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "takeown.exe $ResolvedPath failed: $LASTEXITCODE" | |
| } | |
| } | |
| else { | |
| takeown.exe /f $ResolvedPath | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "takeown $ResolvedPath failed: $LASTEXITCODE" | |
| } | |
| } | |
| icacls.exe $ResolvedPath /grant administrators:F /t | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "icacls $ResolvedPath failed: $LASTEXITCODE" | |
| } | |
| } | |
| } | |
| } | |
| end { | |
| Write-Verbose 'Set-SelfOwnership end' | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Display disk information in human readable format. | |
| #> | |
| function Get-DiskUsage { | |
| Get-Volume ` | |
| | Where-Object DriveLetter -ne $null ` | |
| | Sort-Object DriveLetter ` | |
| | Select-Object -Property DriveLetter, FileSystemLabel, ` | |
| @{Label = 'FreeGb'; Expression = { ($_.SizeRemaining / 1GB).ToString('F2') } }, ` | |
| @{Label = 'TotalGb'; Expression = { ($_.Size / 1GB).ToString('F2') } }, ` | |
| @{Label = 'Used %'; Expression = { | |
| $pct = 100 - ($_.SizeRemaining / $_.Size) * 100 | |
| $blocks = [Math]::Floor($pct / 10) | |
| '{0,6:F2}% {1}{2}' -f $pct, ('■' * $blocks), ('□' * (10 - $blocks)) | |
| } | |
| } ` | |
| | Format-Table | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Open git repo homepage. | |
| .DESCRIPTION | |
| Run from anywhere inside of a local git project. | |
| #> | |
| function Invoke-GitRepo { | |
| Param( | |
| # Launch browser to root of repo? | |
| [switch]$Root | |
| ) | |
| if ($origin = [Uri](git.exe config --get remote.origin.url)) { | |
| if ($Root) { | |
| $uri = $origin.AbsoluteUri | |
| } | |
| else { | |
| $uri = [uri]($origin.AbsoluteUri + '?path=/' + (git.exe rev-parse --show-prefix)) | |
| } | |
| Start-Process -FilePath $uri | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Backup modified git files | |
| #> | |
| function gitbackup { | |
| Param([Parameter(mandatory = $true, position = 0)][string]$Destination) | |
| $backupDir = (Join-Path $Destination (Get-Date -Format FileDateTime)) | |
| git.exe status --porcelain=v1 | Where-Object { $_ -notlike 'D *' } | ForEach-Object { | |
| $item = $_.SubString(3) | |
| Copy-Item -LiteralPath $item -Destination (New-Item -Type Directory -Force (Join-Path $backupDir (Split-Path -Parent $item))) -Verbose | |
| git.exe status | Out-File -Encoding UTF8 (Join-Path $backupDir 'gitstatus.txt') | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Combination of "git grep" and "git blame". | |
| #> | |
| function Get-GitGrepBlame { | |
| Param([Parameter(Mandatory)][string]$Query) | |
| # grep "--null" uses null separators, blame "-c" uses tab separators. | |
| git.exe --no-pager grep --null --line-number $Query ` | |
| | Select-String -Pattern '(?<Filename>[^\0]+)\0(?<Line>[1-9][0-9]*)\0\s*(?<Text>.+)' ` | |
| | Select-Object -ExpandProperty Matches ` | |
| | ForEach-Object { | |
| $filename = $_.Groups['Filename'].Value | |
| $lineno = [int]$_.Groups['Line'].Value | |
| # We'll grab line text from blame instead ("$line = $_.Groups['Text'].Value") | |
| git.exe --no-pager blame -c --show-name --show-number -L "$lineno,$lineno" -- $filename ` | |
| | Select-String -Pattern '(?<Hash>[0-9a-fA-F]+)\t\((?<Author>.+)\t(?<Date>.+)\t\s*(?<Line>[1-9][0-9]*)\)\s*(?<Text>.+)' ` | |
| | Select-Object -ExpandProperty Matches ` | |
| | ForEach-Object { | |
| [PSCustomObject]@{ | |
| Hash = $_.Groups['Hash'].Value | |
| Author = $_.Groups['Author'].Value | |
| # DateTime cast will convert to local time. | |
| Date = [DateTime]$_.Groups['Date'].Value | |
| Path = $filename | |
| Line = [int]$_.Groups['Line'].Value | |
| Text = $_.Groups['Text'].Value | |
| } | |
| } | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| "Pretty prints" and displays .gitconfig file. | |
| #> | |
| function Get-GitConfig { | |
| param([switch]$Local) | |
| $params = @('--no-pager', 'config', '--list') | |
| if ($Local) { | |
| $params += '--local' | |
| } | |
| $config = foreach ($line in git.exe @params) { | |
| if ($line -match '(?<section>.+?)(\.(?<subsection>.+))?\.(?<variable>.+?)=(?<value>.+)') { | |
| [PSCustomObject]@{ | |
| Section = $matches['section'] | |
| SubSection = $matches['subsection'] | |
| Variable = $matches['variable'] | |
| Value = $matches['value'] | |
| } | |
| } | |
| else { | |
| Write-Warning "Invalid line? $line" | |
| } | |
| } | |
| foreach ($item in $config | Group-Object -Property Section, SubSection) { | |
| # read section, subsection from first item in group | |
| $Section, $SubSection = $item.Group[0].Section, $item.Group[0].SubSection | |
| if ($SubSection) { | |
| '[{0} "{1}"]' -f $Section, $SubSection | |
| } | |
| else { | |
| '[{0}]' -f $Section | |
| } | |
| foreach ($entry in $item.Group) { | |
| "{0}{1} = {2}" -f "`t", $entry.Variable, $entry.Value | |
| } | |
| } | |
| } | |
| function Format-GitConfig { | |
| <# | |
| .DESCRIPTION | |
| Formats and outputs the Git configuration in a structured manner. | |
| .EXAMPLE | |
| .\Format-GitConfig.ps1 | |
| Outputs the Git configuration grouped by sections and sorted. | |
| #> | |
| $config = foreach ($line in git.exe --no-pager config --global --list | Sort-Object -Unique) { | |
| if ($line -match '^\s*(?<section>\w*)\.(?<subsection>\w*)\.(?<key>\w*)\s*=\s*(?<value>.+)') { | |
| # e.g. difftool.winmerge.name=WinMerge | |
| [PSCustomObject]@{ | |
| Section = $matches['section'] | |
| Subsection = $matches['subsection'] | |
| Key = $matches['key'] | |
| Value = $matches['value'] | |
| } | |
| } | |
| elseif ($line -match '^\s*(?<section>\w*)\.(?<key>\w*)\s*=\s*(?<value>.+)') { | |
| # e.g. color.ui=auto | |
| [PSCustomObject]@{ | |
| Section = $matches['section'] | |
| Subsection = $null | |
| Key = $matches['key'] | |
| Value = $matches['value'] | |
| } | |
| } | |
| else { | |
| Write-Warning "Invalid line? $line" | |
| } | |
| } | |
| # $config | Sort-Object Section, Variable | Format-List; return | |
| foreach ($line in $config | Group-Object -Property Section, SubSection | Sort-Object Name) { | |
| $section, $subsection = $line.Group[0].Section, $line.Group[0].Subsection | |
| if ($subsection) { | |
| "[$section ""$subsection""]" | |
| } | |
| else { | |
| "[$section]" | |
| } | |
| foreach ($item in $line.Group) { | |
| "`t{0} = {1}" -f $item.Key, $item.Value | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Write-BoxedMessage { | |
| Param( | |
| [Parameter(Mandatory)][string]$Message, | |
| [ValidateRange(30, 37)][string]$ColorCode = 34 | |
| ) | |
| $Color = "`e[0;${ColorCode}m" | |
| $lines = $Message -split "`n" | |
| $maxLength = ($lines | Measure-Object -Maximum -Property Length).Maximum | |
| $separator = '─' * ($maxLength + 2) | |
| Write-Host "$Color┌$separator┐`e[0m" | |
| foreach ($line in $lines) { | |
| $paddedLine = $line.PadRight($maxLength) | |
| Write-Host "$Color│`e[0m $paddedLine $Color│`e[0m" | |
| } | |
| Write-Host "$Color└$separator┘`e[0m`n" | |
| } | |
| <# | |
| .SYNOPSIS | |
| Creates a folder and cds into it. | |
| #> | |
| function mdcd { | |
| Param([Parameter(Mandatory)][string]$Path) | |
| if (New-Item -Path $Path -ItemType Directory -Force) { | |
| Push-Location -LiteralPath $Path | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Create a PowerShell comment using figlet <https://github.com/lukesampson/figlet> | |
| .DESCRIPTION | |
| Will use "small" figlet font, with maximum 120 character width. | |
| #> | |
| function Write-Figlet { | |
| [CmdletBinding()] | |
| Param( | |
| # Message(s) to print, can be from pipeline. | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline)][ValidateNotNullOrEmpty()] | |
| [string[]]$Message, | |
| # Language comment indicator, '' for none. | |
| [string]$CommentIndicator = '# ', | |
| # Maximum width of message | |
| [uint]$OutputWidth = 80, | |
| # Font to use, see "figlet.exe -list", e.g. 'slant' | |
| [string]$FontName = 'small' | |
| ) | |
| begin {} | |
| process { | |
| foreach ($line in $Message) { | |
| figlet.exe -w $OutputWidth -f $FontName $line | ForEach-Object { | |
| "$CommentIndicator $_" | |
| } | |
| $CommentIndicator | |
| } | |
| } | |
| end {} | |
| } | |
| <# | |
| .SYNOPSIS | |
| Write text in a bold unicode font, e.g. 𝐇𝐞𝐥𝐥𝐨 𝐖𝐨𝐫𝐥𝐝! | |
| #> | |
| function Write-Bold { | |
| [CmdletBinding()] | |
| Param( | |
| # Message(s) to print, can be from pipeline. | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline)][ValidateNotNullOrEmpty()] | |
| [string[]]$Message | |
| ) | |
| begin { | |
| # Mathematical Alphanumeric Symbols Block https://unicodeplus.com/block/1D400 | |
| # Mathematical Operators Block https://unicodeplus.com/block/2200 | |
| # Miscellaneous Mathematical Symbols-A Block https://unicodeplus.com/block/27C0 | |
| # Miscellaneous Mathematical Symbols-B Block https://unicodeplus.com/block/2980 | |
| # Supplemental Mathematical Operators Block https://unicodeplus.com/block/2A00 | |
| # Can't use dictionary, as it is case insensitive by default | |
| $hash = New-Object System.Collections.Hashtable ([StringComparer]::Ordinal) | |
| $hash[[char]'-'] = "`u{22EF}" | |
| $hash[[char]'('] = "`u{27EE}" | |
| $hash[[char]')'] = "`u{27EF}" | |
| $hash[[char]'['] = "`u{27E6}" | |
| $hash[[char]']'] = "`u{27E7}" | |
| $hash[[char]'|'] = "`u{2999}" | |
| $hash[[char]'0'] = "`u{1D7EC}" | |
| $hash[[char]'1'] = "`u{1D7ED}" | |
| $hash[[char]'2'] = "`u{1D7EE}" | |
| $hash[[char]'3'] = "`u{1D7EF}" | |
| $hash[[char]'4'] = "`u{1D7F0}" | |
| $hash[[char]'5'] = "`u{1D7F1}" | |
| $hash[[char]'6'] = "`u{1D7F2}" | |
| $hash[[char]'7'] = "`u{1D7F3}" | |
| $hash[[char]'8'] = "`u{1D7F4}" | |
| $hash[[char]'9'] = "`u{1D7F5}" | |
| $hash[[char]'A'] = "`u{1D400}" | |
| $hash[[char]'a'] = "`u{1D41A}" | |
| $hash[[char]'B'] = "`u{1D401}" | |
| $hash[[char]'b'] = "`u{1D41B}" | |
| $hash[[char]'C'] = "`u{1D402}" | |
| $hash[[char]'c'] = "`u{1D41C}" | |
| $hash[[char]'D'] = "`u{1D403}" | |
| $hash[[char]'d'] = "`u{1D41D}" | |
| $hash[[char]'E'] = "`u{1D404}" | |
| $hash[[char]'e'] = "`u{1D41E}" | |
| $hash[[char]'F'] = "`u{1D405}" | |
| $hash[[char]'f'] = "`u{1D41F}" | |
| $hash[[char]'G'] = "`u{1D406}" | |
| $hash[[char]'g'] = "`u{1D420}" | |
| $hash[[char]'H'] = "`u{1D407}" | |
| $hash[[char]'h'] = "`u{1D421}" | |
| $hash[[char]'I'] = "`u{1D408}" | |
| $hash[[char]'i'] = "`u{1D422}" | |
| $hash[[char]'J'] = "`u{1D409}" | |
| $hash[[char]'j'] = "`u{1D423}" | |
| $hash[[char]'K'] = "`u{1D40A}" | |
| $hash[[char]'k'] = "`u{1D424}" | |
| $hash[[char]'L'] = "`u{1D40B}" | |
| $hash[[char]'l'] = "`u{1D425}" | |
| $hash[[char]'M'] = "`u{1D40C}" | |
| $hash[[char]'m'] = "`u{1D426}" | |
| $hash[[char]'N'] = "`u{1D40D}" | |
| $hash[[char]'n'] = "`u{1D427}" | |
| $hash[[char]'O'] = "`u{1D40E}" | |
| $hash[[char]'o'] = "`u{1D428}" | |
| $hash[[char]'P'] = "`u{1D40F}" | |
| $hash[[char]'p'] = "`u{1D429}" | |
| $hash[[char]'Q'] = "`u{1D410}" | |
| $hash[[char]'q'] = "`u{1D42A}" | |
| $hash[[char]'R'] = "`u{1D411}" | |
| $hash[[char]'r'] = "`u{1D42B}" | |
| $hash[[char]'S'] = "`u{1D412}" | |
| $hash[[char]'s'] = "`u{1D42C}" | |
| $hash[[char]'T'] = "`u{1D413}" | |
| $hash[[char]'t'] = "`u{1D42D}" | |
| $hash[[char]'U'] = "`u{1D414}" | |
| $hash[[char]'u'] = "`u{1D42E}" | |
| $hash[[char]'V'] = "`u{1D415}" | |
| $hash[[char]'v'] = "`u{1D42F}" | |
| $hash[[char]'W'] = "`u{1D416}" | |
| $hash[[char]'w'] = "`u{1D430}" | |
| $hash[[char]'X'] = "`u{1D417}" | |
| $hash[[char]'x'] = "`u{1D431}" | |
| $hash[[char]'Y'] = "`u{1D418}" | |
| $hash[[char]'y'] = "`u{1D432}" | |
| $hash[[char]'Z'] = "`u{1D419}" | |
| $hash[[char]'z'] = "`u{1D433}" | |
| } | |
| process { | |
| foreach ($line in $Message) { | |
| # StringBuilder is much faster than string concatenation | |
| $sb = [Text.StringBuilder]::new() | |
| foreach ($char in $line.ToCharArray()) { | |
| $u = $hash[$char] | |
| $sb.Append(@($char, $u)[$null -ne $u ]) | Out-Null | |
| } | |
| $sb.ToString() | |
| } | |
| } | |
| end {} | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| if ($env:VSCODE_INJECTION) { | |
| Write-Host -ForegroundColor Yellow 'Running inside VSCode Terminal.' | |
| return | |
| } | |
| if ($host.Name -ne 'ConsoleHost') { | |
| Write-Host -ForegroundColor Yellow 'Not running in consolehost.' | |
| return | |
| } | |
| # ___ _ _ | |
| # | __| _ _ _ __| |_(_)___ _ _ ___ | |
| # | _| || | ' \/ _| _| / _ \ ' \(_-< | |
| # |_| \_,_|_||_\__|\__|_\___/_||_/__/ | |
| # | |
| # | |
| function Show-ColorTable { | |
| Param([switch]$ShowAll) | |
| if (-not $ShowAll) { | |
| # Inspired by https://windowsterminalthemes.dev | |
| $fgColors = @('Black', 'DarkGray', 'DarkRed', 'Red', 'DarkGreen', 'Green', ` | |
| 'DarkYellow', 'Yellow', 'DarkBlue', 'Blue', 'DarkMagenta', 'Magenta', ` | |
| 'DarkCyan', 'Cyan', 'Gray', 'White') | |
| $bgColors = @('Black', 'DarkGray', 'DarkRed', 'DarkGreen', 'DarkYellow', ` | |
| 'DarkBlue', 'DarkMagenta', 'DarkCyan', 'Gray', 'Black' ) | |
| foreach ($fgcolor in $fgColors) { | |
| foreach ($bgcolor in $bgColors) { | |
| Write-Host -NoNewLine -ForegroundColor $fgcolor -BackgroundColor $bgcolor 'gYw' | |
| Write-Host -NoNewline ' ' | |
| } | |
| Write-Host '' | |
| } | |
| } | |
| else { | |
| $colors = [enum]::GetValues([ConsoleColor]) | |
| foreach ($bgcolor in $colors) { | |
| foreach ($fgcolor in $colors) { | |
| $fg, $bg = ($fgcolor -replace 'Dark', 'Dk'), ($bgcolor -replace 'Dark', 'Dk') | |
| Write-Host -ForegroundColor $fgcolor -BackgroundColor $bgcolor -NoNewLine "|$fg" | |
| } | |
| Write-Host " on $bg" | |
| } | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Download latest PowerShell. | |
| .DESCRIPTION | |
| Will locate and download latest version of PowerShell. | |
| #> | |
| function DownloadLatestPS { | |
| param([Parameter(Mandatory = $true)] [string]$Folder) | |
| # Speed up Invoke-WebRequest calls | |
| $oldpp = $ProgressPreference | |
| $ProgressPreference = 'SilentlyContinue' | |
| $json = (Invoke-WebRequest -uri 'https://api.github.com/repos/PowerShell/PowerShell/releases/latest').Content | ConvertFrom-Json | |
| $psUri = [uri]($json.assets | Where-Object name -Like 'PowerShell-*-win-x64.msi').browser_download_url | |
| "Downloading: $($psUri.AbsoluteUri)" | |
| Invoke-WebRequest $psUri.AbsoluteUri -OutFile (Join-Path $Folder $psuri.Segments[-1]) | |
| $ProgressPreference = $oldpp | |
| } | |
| <# | |
| .SYNOPSIS | |
| Start notepad. | |
| .DESCRIPTION | |
| Will use notepad4. | |
| #> | |
| function Invoke-notepad { | |
| Start-Process -NoNewWindow -FilePath 'notepad4.exe' -ArgumentList $args | |
| } | |
| function Get-UninstallPath { | |
| param( | |
| [Parameter(Mandatory = $true)] [string]$ProductId | |
| ) | |
| $regPath = "/SOFTWARE/Microsoft/Windows/CurrentVersion/Uninstall/$ProductId" | |
| # Check user location first | |
| $path = Get-ItemProperty -Path "HKCU:$regPath" -Name 'InstallLocation' -ErrorAction SilentlyContinue | |
| if (-not $path) { | |
| # Check system location next | |
| $path = Get-ItemProperty -Path "HKLM:$regPath" -Name 'InstallLocation' -ErrorAction SilentlyContinue | |
| } | |
| $path.InstallLocation | |
| } | |
| <# | |
| .SYNOPSIS | |
| Kill a process. | |
| .DESCRIPTION | |
| Will try nicely first. Specify -Force to force. | |
| #> | |
| function Invoke-Kill { | |
| param( | |
| [Parameter(Mandatory = $true)] [string]$Name, | |
| [switch]$Force | |
| ) | |
| $process = Get-Process -Name $Name -ErrorAction SilentlyContinue | |
| if ($process.Count -eq 0) { | |
| 'Process not found' | |
| } | |
| elseif ($process.Count -eq 1) { | |
| $description = $process.Description | |
| $processId = $process.Id | |
| $mainWindowTitle = $process.MainWindowTitle | |
| # try gracefully first | |
| $process.CloseMainWindow() | Out-Null | |
| if ($Force -and -not $process.HasExited) { | |
| Stop-Process -Force -Name $Name | |
| } | |
| if ($process.HasExited) { | |
| 'process {0} ({1}) - ''{2}'' killed' -f $description, $processId, $mainWindowTitle | |
| } | |
| } | |
| else { | |
| 'Multiple instances running: {0}' -f ($process.Id -join ' ') | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Path tools. | |
| .DESCRIPTION | |
| Doesn't persist changes. | |
| #> | |
| function Path { | |
| param( | |
| [Parameter(ParameterSetName = 'List')] | |
| [switch]$List, | |
| [Parameter(ParameterSetName = 'Add')] | |
| [switch]$Add, | |
| [Parameter(ParameterSetName = 'Add')] | |
| [switch]$Top = $False, | |
| [Parameter(ParameterSetName = 'Remove')] | |
| [switch]$Remove, | |
| [Parameter(ParameterSetName = 'Add', Mandatory = $True, Position = 0)] | |
| [Parameter(ParameterSetName = 'Remove', Mandatory = $True, Position = 0)] | |
| [string]$Path | |
| ) | |
| if ($List) { | |
| (Get-ChildItem env:PATH).Value -split ';' | |
| return | |
| } | |
| if ($Add) { | |
| $Path = $Path.TrimEnd('\') | |
| $paths = (Get-ChildItem env:PATH).Value -split ';' | |
| if ($paths -notcontains $Path -and $paths -notcontains "$Path\") { | |
| if ($Top) { | |
| $env:PATH = "$Path;$env:PATH" | |
| } | |
| else { | |
| $env:PATH = "$env:PATH;$Path" | |
| } | |
| } | |
| return | |
| } | |
| if ($Remove) { | |
| $Path = $Path.TrimEnd('\') | |
| $newPath = @() | |
| (Get-ChildItem env:PATH).Value -split ';' | ForEach-Object { | |
| if ($_ -notlike $Path -and $_ -notlike "$Path\") { | |
| $newPath += $_ | |
| } | |
| } | |
| $env:PATH = $newPath -join ';' | |
| return | |
| } | |
| } | |
| <# | |
| .SYNOPSIS | |
| rsync wrapper. | |
| .DESCRIPTION | |
| Requires cwrsync to be installed. | |
| .EXAMPLE | |
| rsync -Source 'C:\Users\jeffm\Downloads\Raise' -Hostname '192.168.10.11' -User 'jeff' -Destination '/media/usb/Music/Earth, Wind & Fire' | |
| #> | |
| function rsync { | |
| param( | |
| [Parameter(Mandatory = $True)] [string]$Source, | |
| [Parameter(Mandatory = $True)] [string]$Destination, | |
| [Parameter(Mandatory = $True)] [string]$User, | |
| [Parameter(Mandatory = $True)] $Hostname | |
| ) | |
| # Use cwrsync | |
| $cwrsync_path = scoop.ps1 prefix cwrsync | |
| if (-not $cwrsync_path) { | |
| 'cwrsync not found' | |
| return | |
| } | |
| $cwrsync_path += '\bin' | |
| # https://www.itefix.net/content/rsync-does-not-recognize-windows-paths-correct-manner | |
| $Source = $Source -replace '\\', '/' | |
| if ($Source -match '^[A-Za-z]:') { | |
| $Source = '/cygdrive/{0}{1}' -f $Source[0], $Source.Substring(2) | |
| } | |
| $Destination = "$User@${Hostname}:$Destination" | |
| "Source: $Source" | |
| "Destination: $Destination" | |
| & "$cwrsync_path\rsync.exe" --rsh="$cwrsync_path/ssh.exe" --progress --human-readable --verbose --recursive --dirs $Source $Destination | |
| } | |
| <# | |
| .SYNOPSIS | |
| Get metadata from all JJazzLab '*.sng' files in current directory. | |
| #> | |
| function Get-JJazzLabMeta { | |
| foreach ($file in (Get-ChildItem -File '*.sng')) { ([xml](Get-Content $file)).Song | Select-Object @{Name = 'BaseName'; Expression = { $file.BaseName } }, spName, spTempo, spComments } | |
| } | |
| <# | |
| .SYNOPSIS | |
| Simulate output of UNIX "sha256hash". | |
| #> | |
| function Invoke-Sha256Hash { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline)] | |
| [string[]]$Files | |
| ) | |
| begin {} | |
| process { | |
| foreach ($file in $Files) { | |
| $target = (Resolve-Path $file).Path | |
| Write-Verbose "Hashing $file" | |
| # Get-ChildItem -Recurse -File | ForEach-Object { | |
| '{0} *{1}' -f (Get-FileHash -Algorithm SHA256 -LiteralPath $target).Hash.ToLower(), (Resolve-Path -Relative $target).Substring(2) -replace '\\', '/' | |
| } | |
| } | |
| end {} | |
| clean {} | |
| } | |
| function Invoke-7zBackup { | |
| param ( | |
| # Root of path to back up recursively, e.g. 'F:\' | |
| [Parameter(Mandatory)][string]$SourcePath, | |
| # Backup path, e.g. 'D:\BACKUP' | |
| [string]$DestinationPath = '.' | |
| ) | |
| Get-Command '7z.exe' -CommandType Application -ErrorAction Stop | Out-Null | |
| $filename = 'Backup {0}' -f (Get-Date -Format '(yyyy-dd-mm)') | |
| $arguments = @( | |
| 'a', | |
| # "-mx=1" reduces compression to the minimum (faster) | |
| '-mx=1', | |
| # "-ms=off" turns off solid mode (faster) | |
| '-ms=off', | |
| # "-mf=off" turns off special compression for exe files (faster) | |
| '-mf=off', | |
| # "-v4g" uses 4GB volumes | |
| '-v4g', | |
| # -x[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : eXclude filenames | |
| '-x!"System Volume Information"', | |
| '-x!"$RECYCLE.BIN"', | |
| '-x!"$RECYCLER"', | |
| '-x!"$WINDOWS.~BT"', | |
| # -r[-|0] : Recurse subdirectories for name search | |
| '-r', | |
| # <archive_name> | |
| ('"{ 0 }"' -f (Join-Path $DestinationPath $filename)), | |
| # <file_names> | |
| (Join-Path $SourcePath '*') | |
| ) | |
| Start-Process -FilePath '7z.exe' -ArgumentList $arguments -NoNewWindow -Wait | |
| } | |
| function Invoke-IpFilterUpdate { | |
| [CmdletBinding()] | |
| param ([switch]$Force) | |
| [uri]$BlockListUri = 'http://list.iblocklist.com/?list=ydxerpxkpcfqjaybcssw&fileformat=dat&archiveformat=zip' | |
| # try default install location | |
| $BlockListPathResolved = Resolve-Path "$env:AppData\qBittorrent" -ErrorAction SilentlyContinue | |
| if (-not $BlockListPathResolved) { | |
| # try scoop install path | |
| $BlockListPathResolved = Resolve-Path '~\scoop\apps\qbittorrent\current\profile\qBittorrent' -ErrorAction SilentlyContinue | |
| } | |
| if ($BlockListPathResolved) { | |
| $ipFilterPath = Join-Path -Path $BlockListPathResolved -ChildPath 'ipfilter.dat' | |
| Write-Host " `e[1;34m*`e[0m Blocklist path: `e[1;35m$ipFilterPath`e[0m" | |
| if (-not $Force) { | |
| Write-Host " `e[1;34m*`e[0m Checking blocklist date on server." | |
| $response = Invoke-WebRequest -Uri $BlockListUri -Method Head -UserAgent 'curl/8.7.1' | |
| $lmServer = [datetime]($response.Headers['Last-Modified'][0]) | |
| $lmLastWriteTime = (Get-ItemProperty -LiteralPath $ipFilterPath -Name LastWriteTime -ErrorAction SilentlyContinue).LastWriteTime | |
| $updateNeeded = $true | |
| if ($lmLastWriteTime) { | |
| $lmLocal = [datetime]$lmLastWriteTime | |
| Write-Host (" `e[1;33m*`e[0m Server: `e[1;36m{0:yyyy-MM-dd}`e[0m; Local: `e[1;36m{1:yyyy-MM-dd}`e[0m" -f $lmServer, $lmLocal) | |
| # Only update if local is at least a day older than server | |
| if (($lmLocal - $lmServer).Days -eq 0) { | |
| $updateNeeded = $false | |
| } | |
| } | |
| } | |
| else { | |
| # -Force specified | |
| Write-Host " `e[1;33m!`e[0m Force specified, forcing update." | |
| $updateNeeded = $true | |
| } | |
| if ($updateNeeded) { | |
| # ZIP file contains a single file, "ydxerpxkpcfqjaybcssw.txt" | |
| $tempIpFilterPath = Join-Path -Path $BlockListPathResolved -ChildPath 'ipfilter.dat.zip' | |
| Write-Host " `e[1;34m*`e[0m Downloading new filter to `e[1;35m$tempIpFilterPath`e[0m" | |
| $response = Invoke-WebRequest -Uri $BlockListUri -UserAgent 'curl/8.7.1' -OutFile $tempIpFilterPath | |
| Write-Host " `e[1;34m*`e[0m Expanding to `e[1;35m$BlockListPathResolved`e[0m" | |
| Expand-Archive -DestinationPath $BlockListPathResolved -LiteralPath $tempIpFilterPath | |
| Remove-Item -LiteralPath $ipFilterPath -ErrorAction SilentlyContinue | |
| Write-Host " `e[1;34m*`e[0m Renaming to `e[1;35mipfilter.dat`e[0m" | |
| Rename-Item -NewName 'ipfilter.dat' -LiteralPath (Join-Path -Path $BlockListPathResolved 'ydxerpxkpcfqjaybcssw.txt') | |
| Remove-Item -LiteralPath $tempIpFilterPath | |
| Write-Host " `e[1;32m✓`e[0m Blocklist updated." | |
| } | |
| else { | |
| Write-Host " `e[1;32m✓`e[0m Everything is up to date!" | |
| } | |
| } | |
| else { | |
| Write-Warning " `e[1;31m!`e[0m Cant determine qBittorrent path." | |
| } | |
| } | |
| # Convert a file to MP3 Using FFMPEG | |
| function ConvertTo-Mp3 { | |
| [CmdletBinding()] | |
| param ( | |
| # "transparent results": | |
| # 0 = 245 kbit/sec avg. | |
| # 1 = 225 kbit/sec avg.. | |
| # 2 = 190 kbit/sec avg. (170-210 kbit/sec). See https://trac.ffmpeg.org/wiki/Encode/MP3 | |
| # 3 = 175 kbit/sec avg. | |
| [ValidateRange(0, 9)][int]$Quality = 2, | |
| # Input file name. Can also use "-FullName" for piping from "Get-ChildItem -File" | |
| [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)] | |
| [Alias('FullName')] | |
| [string] $InputFile, | |
| # Output file name. If not specified, uses <name>.mp3 in current directory. | |
| [Parameter(Position = 1)] | |
| [string] $OutputFile, | |
| # Force overwriting files that exist. | |
| [switch]$Force | |
| ) | |
| begin { | |
| $count = 0 | |
| } | |
| process { | |
| # Don't modify $OutputFile, as we will check it against null with each new file. | |
| $Destination = $OutputFile | |
| if (-not $OutputFile) { | |
| $Destination = ('{0}.mp3' -f (Split-Path -Path $InputFile -LeafBase)) | |
| } | |
| if ((Split-Path -Path $Destination -Extension) -ne '.mp3') { | |
| throw 'Extension must be .mp3' | |
| } | |
| 'Converting "{0}" to "{1}".' -f (Split-Path -Path $InputFile -Leaf), (Split-Path -Path $Destination -Leaf) | |
| # -hide_banner Suppress printing banner. | |
| # -loglevel Set logging level and flags used by the library. | |
| # -stats Log encoding progress/statistics as "info"-level log | |
| # -i input file url | |
| # -vn blocks all video streams of a file from being filtered or being automatically selected or mapped for any output. | |
| # -codec:a Select an encoder. "a" is stream_specifier, followed by codec name. | |
| # -qscale:a Specify codec-dependent fixed quality scale (VBR). "a" is stream_specifier, followed by q value. | |
| $ffmpeg_args = ` | |
| '-hide_banner', ` | |
| '-loglevel', 'error', ` | |
| '-stats', ` | |
| '-i', """$InputFile""", ` | |
| '-vn', ` | |
| '-codec:a', 'libmp3lame', ` | |
| '-qscale:a', $Quality | |
| if ($Force) { | |
| $ffmpeg_args += '-y' | |
| } | |
| # Output filename must be last argument. | |
| $ffmpeg_args += """$Destination""" | |
| # ffmpeg always seems to return 0 in ExitCode, so no way to check for failures, even with "-PassThru". | |
| Start-Process -FilePath 'ffmpeg.exe' -ArgumentList $ffmpeg_args -NoNewWindow -Wait | |
| $count++ | |
| } | |
| end { | |
| "Processed $count files." | |
| } | |
| } | |
| function Format-PowerShell { | |
| # .DESCRIPTION | |
| # format powershell script | |
| Param([Parameter(Mandatory, Position = 0)][string]$Path) | |
| Install-Module -Name PowerShell-Beautifier | |
| Edit-DTWBeautifyScript -SourcePath (Resolve-Path -LiteralPath $Path).Path -IndentType FourSpaces | |
| } | |
| <# | |
| .SYNOPSIS | |
| Decode an ATP safelink | |
| #> | |
| function Convert-SafeLink { | |
| [CmdletBinding()] | |
| Param([Parameter(Mandatory, Position = 0, ValueFromPipeline)][Uri]$Uri) | |
| if ($Uri.Query) { | |
| $query = @{} | |
| # Split query into Name, Unescaped Value pairs. | |
| $Uri.Query.Substring(1) -split '&' | ForEach-Object { | |
| $key, $value = $_.Split('=') | |
| $query[$key] = [URI]::UnescapeDataString($value) | |
| } | |
| [PSCustomObject]@{ | |
| 'Host' = $Uri.Host | |
| 'Uri' = $query['url'] | |
| 'Data' = $query['data'] | |
| } | |
| } | |
| } | |
| function Get-SystemInfo { | |
| # Derived from WinProdKeyFinder: https://github.com/mrpeardotnet/WinProdKeyFinder (DecodeProductKeyWin8AndUp) | |
| $digitalProductId = (Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion').DigitalProductId | |
| # First byte appears to be length | |
| if ($digitalProductId[0] -ne $digitalProductId.Length) { | |
| throw 'Invalid length.' | |
| } | |
| $productId = [Text.Encoding]::UTF8.GetString($digitalProductId[8..30]) | |
| $sku = [Text.Encoding]::UTF8.GetString($digitalProductId[36..48]) | |
| $keyOffset = 52 | |
| # decrypt base24 encoded binary data from $digitalProductId[52..66] $key | |
| $key = $null | |
| $digits = 'BCDFGHJKMPQRTVWXY2346789' | |
| For ($i = 24; $i -ge 0; $i--) { | |
| $index = 0 | |
| For ($j = 14; $j -ge 0; $j--) { | |
| $index = $index * 256 | |
| $index += $digitalProductId[$keyOffset + $j] | |
| $digitalProductId[$keyOffset + $j] = [math]::truncate($index / 24) | |
| $index = $index % 24 | |
| } | |
| $key = $digits[$index] + $key | |
| } | |
| # Replace first character with 'N', split every 5 chars with '-' | |
| $key = ('N' + $key.Substring(1, $key.Length - 1)) -split '(.{5})' -ne '' -join '-' | |
| $currentVersion = Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | |
| $win32os = Get-WmiObject Win32_OperatingSystem | |
| [PSCustomObject]@{ | |
| Edition = $win32os.Caption | |
| Version = $currentVersion.DisplayVersion | |
| OSBuild = '{0}.{1}' -f $currentVersion.CurrentBuild, $currentVersion.UBR | |
| OSArch = $win32os.OSArchitecture | |
| RegisteredOwner = $currentVersion.RegisteredOwner | |
| ProductID = $productId | |
| Sku = $sku | |
| ProductKey = $key | |
| } | |
| } | |
| ####################################################################################################################### | |
| # _ | |
| # _ __ __ _(_)_ _ | |
| # | ' \/ _` | | ' \ | |
| # |_|_|_\__,_|_|_||_| | |
| # | |
| 'download', 'filesystem', 'git', 'messages', 'musescore', 'normalize', 'prompt', 'transcribe', 'update', 'vscode' ` | |
| | ForEach-Object { | |
| "Loading functions from $_.ps1" | |
| . "$PSScriptRoot\$_.ps1" | |
| } | |
| # See also $env:USERPROFILE\OneDrive\Documents\PowerShell\profile.ps1 | |
| Write-BoxedMessage -Message "Profile loaded from $PSScriptRoot`nPowerShell $((Get-Host).Version)" | |
| # Use emacs key bindings. | |
| # Set-PSReadLineOption -EditMode Emacs | |
| # Make TAB completion more like bash - Use up, down arrow to complete command. | |
| Set-PSReadlineKeyHandler -Key UpArrow -Function HistorySearchBackward | |
| Set-PSReadlineKeyHandler -Key DownArrow -Function HistorySearchForward | |
| if ((Get-Command -Name 'fzf.exe' -CommandType Application -ErrorAction SilentlyContinue)) { | |
| # PSFzf: https://github.com/kelleyma49/PSFzf | |
| 'PSFzf' | ForEach-Object { | |
| if (-not (Get-Module -Name $_ -ListAvailable -ErrorAction SilentlyContinue)) { | |
| "Installing $_" | |
| Install-Module -Name $_ -Force | |
| } | |
| } | |
| 'Enabling Ctrl+T, Ctrl+R completion' | |
| # Reverse Search Through PSReadline History (default chord: Ctrl+r) | |
| Set-PsFzfOption -PSReadlineChordProvider 'Ctrl+t' -PSReadlineChordReverseHistory 'Ctrl+r' | |
| # Set-Location Based on Selected Directory (default chord: Alt+c) | |
| $commandOverride = [ScriptBlock] { param($Location) Write-Host $Location } | |
| Set-PsFzfOption -AltCCommand $commandOverride | |
| # Tab Expansion | |
| # Set-PSReadLineKeyHandler -Key Tab -ScriptBlock { Invoke-FzfTabCompletion } | |
| } | |
| Invoke-WingetUpdate | |
| # Add Winget package paths to PATH | |
| $wingetPackagesPath = "$env:LocalAppData\Microsoft\Winget\Packages" | |
| "Adding Winget paths from $wingetPackagesPath" | |
| Get-ChildItem -LiteralPath $wingetPackagesPath -Recurse -Filter '*.exe' -ErrorAction SilentlyContinue ` | |
| | Group-Object DirectoryName ` | |
| | ForEach-Object { | |
| $exes = $_.Group | ForEach-Object { "`e[1m{0}`e[22m" -f (Split-Path -Leaf $_) } | |
| ' {0}: {1}' -f [IO.Path]::GetRelativePath("$env:LocalAppData\Microsoft\Winget\Packages", $_.Name), ($exes -join ', ') | |
| $env:Path += ";$($_.Name)" | |
| } | |
| # Invoke-ScoopUpdate | |
| # ----------------------------------------------------------------------------- | |
| # zoxide, needs to come AFTER setting prompt! | |
| if ((Get-Command -Name 'zoxide.exe' -CommandType Application -ErrorAction SilentlyContinue)) { | |
| 'Adding zoxide completion' | |
| Invoke-Expression -Command $(zoxide.exe init powershell | Out-String) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Convert MuseScore file to PDF. Files are output to current folder. | |
| .EXAMPLE | |
| mkdir PDF; cd PDF | |
| Get-ChildItem -File -LiteralPath '..' -Filter '*.mscz' | ForEach-Object { ConvertFrom-MuseScore -Extract Parts -File $_.FullName } | |
| #> | |
| function ConvertFrom-MuseScore { | |
| param( | |
| # Path to .mscz file | |
| [Parameter(Mandatory)][string]$File, | |
| # What to extract | |
| [ValidateSet('Score', 'ScoreAndParts', 'ScoreAudio', 'Parts', IgnoreCase)] | |
| [string[]]$Extract = @('Score', 'Parts'), | |
| # Path to MuseScore. Defaults to standard install location. | |
| [string]$MuseScorePath = (Join-Path "$env:ProgramFiles" 'MuseScore 4\bin\MuseScore4.exe') | |
| ) | |
| # https://github.com/musescore/MuseScore/issues/22887 | |
| # --export-score-parts / -P CLI options no longer work in 4.x #22887 | |
| $path = Resolve-Path -LiteralPath $File -ErrorAction Stop | |
| Write-Host ('Processing "{0}"...' -f (Split-Path -Path $path -Leaf)) | |
| Write-Host '* Extracting JSON' | |
| $musescoreJsonFile = 'musescore-score-parts.json' | |
| $process = Start-Process -Wait -NoNewWindow -PassThru ` | |
| -WorkingDirectory (Get-Location).Path ` | |
| -FilePath """$MuseScorePath""" ` | |
| -ArgumentList '--score-parts-pdf', """$path""", '--export-to', """$musescoreJsonFile""" | |
| if ($process.ExitCode -ne 0) { | |
| Write-Warning "ExitCode=$($process.ExitCode)" | |
| } | |
| $musescoreJson = Get-Content $musescoreJsonFile | ConvertFrom-Json | |
| $name = Split-Path -LeafBase $musescoreJson.score | |
| if ('ScoreAndParts' -in $Extract) { | |
| $filename = $name + ' [Score and Parts].pdf' | |
| Write-Host "* Extracting score and parts ""$filename""" | |
| # Set-Content -LiteralPath $filename -AsByteStream -Value ([Convert]::FromBase64String($musescoreJson.scoreFullBin)) | |
| # WORKAROUND: | |
| # --score-parts-pdf CLI option outputs double base64-encoded JSON string for .scoreFullBin field #28436 | |
| # https://github.com/musescore/MuseScore/issues/28436 (Fixed in MuseScore 4.6) | |
| $base64string = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($musescoreJson.scoreFullBin)) | |
| Set-Content -LiteralPath $filename -AsByteStream -Value ([Convert]::FromBase64String($base64string)) | |
| } | |
| if ('Score' -in $Extract) { | |
| $filename = $name + ' [Score].pdf' | |
| Write-Host "* Extracting score ""$filename""" | |
| Set-Content -LiteralPath $filename -AsByteStream -Value ([Convert]::FromBase64String($musescoreJson.scoreBin)) | |
| } | |
| if ('Parts' -in $Extract) { | |
| for ($i = 0; $i -lt $musescoreJson.parts.Count; $i++) { | |
| $partname = $musescoreJson.parts[$i] | |
| # Create filename using "score_name [part].pdf", ensuring valid filename. | |
| $filename = ('{0} [{1}].pdf' -f (Split-Path -LeafBase $name), $partname) -replace '[<>:"/\\|?*]', '_' | |
| Write-Host "* Extracting part ""$filename""" | |
| Set-Content -LiteralPath $filename -AsByteStream -Value ([Convert]::FromBase64String($musescoreJson.partsBin[$i])) | |
| } | |
| } | |
| Remove-Item -LiteralPath $musescoreJsonFile | |
| if ('ScoreAudio' -in $Extract) { | |
| $filename = $name + '.mp3' | |
| Write-Host "* Extracting audio ""$filename""..." | |
| $process = Start-Process -Wait -NoNewWindow -PassThru ` | |
| -WorkingDirectory (Get-Location).Path ` | |
| -FilePath """$MuseScorePath""" ` | |
| -ArgumentList """$path""", '--export-to', """$filename""" | |
| if ($process.ExitCode -ne 0) { | |
| Write-Warning "ExitCode=$($process.ExitCode)" | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Normalize a file using FFMPEG | |
| # Normalized file will be named {filename}-normalized.{extension} | |
| function Invoke-Normalize { | |
| # Support -Confirm (ShouldProcess), -WhatIf | |
| [CmdletBinding(SupportsShouldProcess)] | |
| param( | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]]$Path, | |
| [switch]$InPlace | |
| ) | |
| begin { | |
| Get-Command -Name ffmpeg.exe -CommandType Application -ErrorAction Stop | Out-Null | |
| } | |
| process { | |
| foreach ($item in $Path) { | |
| Write-Verbose ('-' * 79) | |
| $resolveditem = Resolve-Path -LiteralPath $item | |
| $itemname = Split-Path -Path $item -Leaf | Split-Path -LeafBase | |
| $itemextension = Split-Path -Path $item -Leaf | Split-Path -Extension | |
| Write-Verbose "Resolved item: $resolveditem" | |
| Write-Verbose "Name: $itemname; Extension: $itemextension" | |
| if (-not $PSCmdlet.ShouldProcess($resolveditem, 'Normalize')) { | |
| continue | |
| } | |
| $ffmpeg_args = ` | |
| # Overwrite output files. | |
| '-y', ` | |
| # Don't show banner | |
| '-hide_banner', ` | |
| # disable console itneraction | |
| '-nostdin', ` | |
| # Input file | |
| '-i', """$resolveditem""", ` | |
| # set audio filters | |
| '-af', '"volumedetect"', ` | |
| # disable video | |
| '-vn', ` | |
| # disable subtitle | |
| '-sn', ` | |
| # disable data | |
| '-dn', ` | |
| # force format | |
| '-f', 'null', ` | |
| # No output file | |
| '-' | |
| $startinfo = New-Object System.Diagnostics.ProcessStartInfo | |
| $startinfo.FileName = 'ffmpeg.exe' | |
| # FFMPEG writes to stderr! | |
| $startinfo.RedirectStandardError = $true | |
| $startinfo.RedirectStandardOutput = $false | |
| $startinfo.UseShellExecute = $false | |
| $startinfo.Arguments = $ffmpeg_args | |
| $proc = New-Object System.Diagnostics.Process | |
| $proc.StartInfo = $startinfo | |
| Write-Verbose "Running ffmpeg with arguments: $($ffmpeg_args -join ' ')" | |
| $proc.Start() | Out-Null | |
| Write-Verbose 'ReadToEnd...' | |
| $out = $proc.StandardError.ReadToEnd() | |
| Write-Verbose 'WaitForExit...' | |
| $proc.WaitForExit() | |
| Write-Verbose "ffmpeg exited with code $($proc.ExitCode)" | |
| if ($proc.ExitCode -ne 0) { | |
| Write-Warning "ffmpeg failed with code $($proc.ExitCode)" | |
| $out | |
| continue | |
| } | |
| # Write-verbose $out | |
| # e.g. "[Parsed_volumedetect_0 @ 0000021cc88cbc00] max_volume: 0.0 dB" | |
| if ($out -notmatch 'max_volume: (?<max_volume>-?[\d\.]+) dB') { | |
| Write-Warning "Unable to determine max_volume of $resolveditem" | |
| continue | |
| } | |
| # Gain is inverse of reported max_volume | |
| $gain = - [decimal]$matches['max_volume'] | |
| if ($gain -le 1.0) { | |
| Write-Output "No gain adjustment required for $resolveditem" | |
| continue | |
| } | |
| $tempfile = Join-Path -Path (Get-Location) -ChildPath "$itemname-normalized$($itemextension)" | |
| Write-Output "Applying gain of $gain dB to $resolveditem" | |
| $ffmpeg_args = ` | |
| # Overwrite output files. | |
| '-y', ` | |
| # Don't show banner | |
| '-hide_banner', ` | |
| # disable console itneraction | |
| '-nostdin', ` | |
| # Input file | |
| '-i', """$resolveditem""", ` | |
| # set audio filters | |
| '-af', """volume=$($gain)dB""", ` | |
| # Output file | |
| """$tempfile""" | |
| $startinfo = New-Object System.Diagnostics.ProcessStartInfo | |
| $startinfo.FileName = 'ffmpeg.exe' | |
| # FFMPEG writes to stderror! | |
| $startinfo.RedirectStandardError = $true | |
| $startinfo.RedirectStandardOutput = $false | |
| $startinfo.UseShellExecute = $false | |
| $startinfo.Arguments = $ffmpeg_args | |
| $proc = New-Object System.Diagnostics.Process | |
| $proc.StartInfo = $startinfo | |
| Write-Verbose "Running ffmpeg with arguments: $($ffmpeg_args -join ' ')" | |
| $proc.Start() | Out-Null | |
| Write-Verbose 'ReadToEnd...' | |
| $out = $proc.StandardError.ReadToEnd() -split "`r`n" | |
| Write-Verbose 'WaitForExit...' | |
| $proc.WaitForExit() | |
| Write-Verbose "ffmpeg exited with code $($proc.ExitCode)" | |
| if ($proc.ExitCode -ne 0) { | |
| Write-Warning "ffmpeg failed with code $($proc.ExitCode)" | |
| $out | |
| Remove-Item $tempfile -ErrorAction SilentlyContinue | |
| continue | |
| } | |
| if ($InPlace) { | |
| $backupItem = "$itemname-backup$itemextension" | |
| Rename-Item -LiteralPath $resolveditem -NewName $backupItem -ErrorAction Stop | |
| Rename-Item -LiteralPath $tempfile -NewName $resolveditem -ErrorAction Stop | |
| Remove-Item -LiteralPath $backupItem -ErrorAction SilentlyContinue | |
| } | |
| } | |
| } | |
| end {} | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Profile for Windows PowerShell (PowerShell 5) | |
| Write-Host ("Profile loaded from {0}" -f $PSCommandPath) | |
| if ($env:VSCODE_INJECTION) { | |
| Write-Host -ForegroundColor Yellow 'Running inside VSCode Terminal' | |
| } | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function TruncatePath { | |
| param([string]$Path, [int]$MaxChars = 80) | |
| $truncated = $Path | |
| if ($Path.Length -gt $MaxChars) { | |
| if (Split-Path $Path -IsAbsolute) { | |
| # truncated is the minimal string that will be shown, e.g. 'C:\...' | |
| $minPath = (Split-Path $Path -Qualifier) + '\...' | |
| # Fit as many subpaths as possible | |
| $fit = $null | |
| while ($true) { | |
| $leaf = Split-Path $Path -Leaf | |
| if ($minPath.Length + 1 + $fit.Length + $leaf.Length -gt $MaxChars) { | |
| break | |
| } | |
| $fit = '\' + $leaf + $fit | |
| $Path = Split-Path $Path -Parent | |
| } | |
| $truncated = $minPath + $fit | |
| } | |
| else { | |
| # Non-absolute path, just truncate as needed. | |
| $truncated = '...' + $Path.Substring(($Path.Length + 3) - $MaxChars) | |
| } | |
| } | |
| $truncated | |
| } | |
| <# | |
| .SYNOPSIS | |
| Prompt | |
| .DESCRIPTION | |
| Indicates if running as admin, and git branch. | |
| #> | |
| function global:Prompt { | |
| $cwd = (Get-Location).Path | |
| if ($cwd.StartsWith($HOME)) { $cwd = $cwd.Replace($HOME, '~') } | |
| $cwd = TruncatePath -Path $cwd -MaxChars 60 | |
| $isAdmin = [bool](([Security.Principal.WindowsIdentity]::GetCurrent()).groups -match 'S-1-5-32-544') | |
| Write-Host @(' PS ', ' ADMIN ')[$isAdmin] -NoNewline -BackgroundColor @('DarkMagenta', 'DarkRed')[$isAdmin] -ForegroundColor White | |
| Write-Host " $cwd " -NoNewline -BackgroundColor DarkYellow -ForegroundColor Black | |
| try { | |
| $gitBranch = (git.exe symbolic-ref HEAD 2>&1) | |
| if ($gitBranch.ToString() -notmatch 'fatal:') { | |
| $gitBranch = Split-Path -Leaf $gitBranch | |
| $isDirty = (git.exe status --porcelain).Count -ne 0 | |
| Write-Host " $gitBranch " -NoNewline -Background @('DarkBlue', 'DarkCyan')[$isDirty] -ForegroundColor White | |
| } | |
| } | |
| catch {} | |
| Write-Host '' | |
| '> ' | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Fix a Transcribe XSC file to my desired default settings. | |
| .DESCRIPTION | |
| This script reads a Transcribe XSC file, modifies some settings to my preferred defaults, | |
| and writes the changes back to the file. | |
| .PARAMETER Path | |
| The path to the XSC file to fix. | |
| .NOTES | |
| The XSC file format is meh, so Read-Xsc is a bit weird. | |
| .EXAMPLE | |
| . c:\Users\jeffm\.local\bin\FixXsc.ps1 | |
| Get-ChildItem -File '*.xsc' | Process-Xsc | |
| #> | |
| function Read-Xsc { | |
| [CmdletBinding()] | |
| param ([Parameter(Mandatory)][string]$Path) | |
| $data = [ordered]@{} | |
| $version = $null | |
| $transcribe = $null | |
| $section = $null | |
| Get-Content -LiteralPath $Path -ErrorAction Stop | ForEach-Object { | |
| switch -regex ($_) { | |
| # [Signature] Document Version | |
| '^XSC Transcribe.Document Version (\d+\.\d+)' { | |
| $version = $matches[1] | |
| Write-Host "XSC Version: $version" | |
| continue | |
| } | |
| # [Signature] System Info (transcribe platform,v_major,v_minor,?,?,? | |
| '^Transcribe!,(.+)$' { | |
| $transcribe = $matches[1] | |
| Write-Host "Transcribe Info: $transcribe" | |
| continue | |
| } | |
| # [Header] SectionStart | |
| '^SectionStart,(.+)$' { | |
| if ($section) { throw "Unclosed section: $section" } | |
| $section = $matches[1] | |
| $data[$section] = [ordered]@{} | |
| continue | |
| } | |
| # [Header] SectionEnd | |
| '^SectionEnd,(.+)$' { | |
| if ($matches[1] -ne $section) { throw "Unmatched SectionEnd: $($matches[1])" } | |
| $section = $matches[1] | |
| $section = $null | |
| continue | |
| } | |
| # [Values] key,value | |
| '^(.+?),(.+)$' { | |
| if (-not $section) { throw "key,value not in section!: $_" } | |
| $key, $value = $matches[1, 2] | |
| if ($section -eq 'Loops' -and $key -eq 'L') { | |
| # e.g. "L,15,0,0,0,,White,,0:00:00.000000,0:00:00.000000" | |
| # use "L,15" as key, rest as value. | |
| $i = $value.IndexOf(',') | |
| $data[$section]['L,' + $value.Substring(0, $i)] = $value.Substring($i + 1) | |
| } | |
| else { | |
| $data[$section][$key] = $value | |
| } | |
| continue | |
| } | |
| } | |
| } | |
| [PSCustomObject]@{ | |
| Version = $version | |
| Transcribe = $transcribe | |
| Data = $data | |
| } | |
| } | |
| function Write-Xsc { | |
| param ([Parameter(Mandatory)]$XSC) | |
| # Write header | |
| "XSC Transcribe.Document Version $($XSC.Version)" | |
| "Transcribe!,$($XSC.Transcribe)" | |
| # Each section is in format: | |
| # SectionStart,<section_name> | |
| # Key,Value | |
| # SectionEnd,<section_name> | |
| $data = $XSC.Data | |
| Foreach ($section in $data.get_keys()) { | |
| '' | |
| "SectionStart,$section" | |
| foreach ($key in $data[$section].get_keys()) { | |
| '{0},{1}' -f $key, $data[$section][$key] | |
| } | |
| "SectionEnd,$section" | |
| } | |
| } | |
| ## Main | |
| function Process-Xsc { | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory, ValueFromPipelineByPropertyName)] | |
| [string[]]$FullName | |
| ) | |
| begin { | |
| Write-Host "Starting XSC processing..." | |
| Write-Host "Files to process: $($Paths.Count)" | |
| } | |
| process { | |
| foreach ($file in $FullName) { | |
| try { | |
| Write-Host "Processing file: $file" | |
| $xsc = Read-Xsc -Path $file | |
| # The values I prefer | |
| $xsc.Data['Main']['SaveDate'] = (Get-Date -Format 'yyyy-MM-dd HH:mm') | |
| $xsc.Data['Main']['WindowSize'] = '1706|979|858|410,0' | |
| $xsc.Data['Main']['ViewList'] = '1,0,0' | |
| $xsc.Data['View0']['ShowSpectrum'] = '0' | |
| $xsc.Data['View0']['ShowGuessNotes'] = '0' | |
| $xsc.Data['View0']['ShowGuessChords'] = '0' | |
| $xsc.Data['View0']['ShowAsMono'] = '1' | |
| $xsc.Data['View0']['ShowDB'] = '0' | |
| $xsc.Data['View0']['ViewSplitterPos'] = '0.792035' | |
| $xsc.Data['View0']['HorizProfileZoom'] = '0.015929' | |
| Write-Host "Writing fixed XSC file to: $file" | |
| Write-Xsc -XSC $xsc | Out-File -LiteralPath $file | |
| } | |
| catch { | |
| Write-Warning "Failed to read XSC file at path '$file': $_" | |
| } | |
| } | |
| } | |
| end { | |
| Write-Host "Finished XSC processing." | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| function Invoke-WingetUpdate { | |
| Param([switch]$Force) | |
| if (-not (Get-Command -Name 'Get-WinGetPackageUpdate' -ErrorAction SilentlyContinue)) { | |
| # Install-Module -Name Microsoft.WinGet.Client -RequiredVersion 0.2.1 | |
| Write-Warning 'WinGet module not installed. Skipping.' | |
| return | |
| } | |
| if (-not $Force) { | |
| if (((Get-Date) - [DateTime](Get-Content "~/.winget-lastcheck" -ErrorAction SilentlyContinue)).TotalDays -lt 7) { | |
| 'Winget update check was run recently. Skipping.' | |
| return | |
| } | |
| Set-Content "~/.winget-lastcheck" -ErrorAction SilentlyContinue (Get-Date -Format 'O') | |
| } | |
| 'Checking for winget updates' | |
| '' | |
| $updates = Get-WinGetPackageUpdate | Where-Object { $_.Source -eq 'winget' } | |
| if ($updates.Count -eq 0) { | |
| return | |
| } | |
| 'The following Winget packages have updates available:' | |
| $updates | ForEach-Object { | |
| "• {0} ({1} -> {2})" -f $_.ID, $_.Version, $_.Available | |
| } | |
| '' | |
| if (-not $Force) { | |
| for ($i = 0; $i -lt 3; $i++) { | |
| Write-Host -NoNewline "WinGet updates are available. Waiting for $(3-$i) second$(@('','s')[$i -le 1]), press a key to skip ...`r" | |
| if ($Host.UI.RawUI.KeyAvailable -and $host.UI.RawUI.ReadKey('NoEcho,IncludeKeyUp,IncludeKeyDown').KeyDown) { | |
| Write-Warning 'Updating cancelled.' | |
| return | |
| } | |
| Start-Sleep -Seconds 1 | |
| } | |
| '' | |
| } | |
| 'Found updates. Updating now.' | |
| '' | |
| # $wingetArgs = 'update', '--all', '--source', 'winget', '--accept-package-agreements', '--accept-source-agreements', '--include-unknown' | |
| # & sudo.exe winget.exe @wingetArgs | |
| $updates | ForEach-Object { | |
| "• Updating {0} ..." -f $_.ID | |
| Update-WinGetPackage -ID $_.ID -Exact | |
| } | |
| } | |
| function Invoke-ScoopUpdate { | |
| Param([switch]$Force) | |
| if (-not (Get-Command -Name 'scoop.ps1' -CommandType ExternalScript -ErrorAction SilentlyContinue)) { | |
| return | |
| } | |
| if (-not $Force) { | |
| if (((Get-Date) - [DateTime](Get-Content "~/.scoop-lastcheck" -ErrorAction SilentlyContinue)).TotalDays -lt 7) { | |
| 'scoop update check was run recently. Skipping.' | |
| return | |
| } | |
| Set-Content "~/.scoop-lastcheck" -ErrorAction SilentlyContinue (Get-Date -Format 'O') | |
| } | |
| 'Checking for scoop updates' | |
| scoop update * | |
| scoop cleanup * | |
| # Enable completion in current shell. | |
| # scoop install extras/scoop-completion | |
| $modulePath = "$env:USERPROFILE\scoop\modules\scoop-completion" | |
| if (Test-Path -LiteralPath $modulePath -PathType Container) { | |
| 'Installing scoop-completion' | |
| Import-Module $modulePath | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Launch vscode | |
| .DESCRIPTION | |
| Unlike code.cmd, this supports wildcards. | |
| .EXAMPLE | |
| Get-ChildItem *.txt | Invoke-Code -Confirm | |
| #> | |
| function Invoke-Code { | |
| # Support -Confirm, -WhatIf | |
| [CmdletBinding(SupportsShouldProcess)] | |
| param( | |
| # Files to edit. Wildcards supported. | |
| [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
| [string[]]$Path, | |
| # Whether to create a new VSCode isntance. | |
| [switch]$NewWindow, | |
| # Command to launch code. Typically 'code.cmd' | |
| [string]$CodeCommand = 'code.cmd' | |
| ) | |
| begin { | |
| Write-Verbose 'Invoke-Code: begin' | |
| # Fail fast if $CodeCommand can't be located. | |
| Get-Command -Name $CodeCommand -CommandType Application -ErrorAction Stop | Out-Null | |
| $codeArgs = @() | |
| } | |
| process { | |
| Write-Verbose 'Invoke-Code: process' | |
| foreach ($item in $Path) { | |
| if ($item -notmatch '[\*\?]' -and -not (Test-Path $item -PathType Leaf)) { | |
| # Add directory names or files that don't exist as-is | |
| if ($PSCmdlet.ShouldProcess($item, 'Edit with code')) { | |
| Write-Verbose "Adding non-existent file: $item" | |
| $codeArgs += $item | |
| } | |
| } | |
| else { | |
| # Support wildcards | |
| Get-ChildItem -Path $item | Select-Object -ExpandProperty FullName | ForEach-Object { | |
| if ($PSCmdlet.ShouldProcess($_, 'Edit with code')) { | |
| Write-Verbose "Adding file: $_" | |
| $codeArgs += $_ | |
| } | |
| } | |
| } | |
| } | |
| } | |
| end { | |
| Write-Verbose 'Invoke-Code: end' | |
| if ($PSCmdlet.MyInvocation.BoundParameters.ContainsKey('WhatIf')) { | |
| # -WhatIf requires no additional processing. | |
| return | |
| } | |
| if ($codeArgs.Count -eq 0) { | |
| Write-Warning 'No matching files found.' | |
| return | |
| } | |
| if ($NewWindow) { | |
| $codeArgs += '--new-window' | |
| } | |
| Write-Verbose "Launching $CodeCommand $codeArgs" | |
| & $CodeCommand @codeArgs | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment