PowershellFunctions007.psm1
if ($PSEdition -ne 'Core' -or $PSVersionTable.PSVersion.Major -lt 7) { throw "This module is only supported in PowerShell 7 or higher (PSEdition 'Core')." } function Get-IanaTimeZone { $win_tz = (Get-TimeZone).Id $iana_tz = $null # Method 1: .NET TimeZoneInfo API (PowerShell 7.2+ / .NET 6+) if ([System.TimeZoneInfo].GetMethod("TryConvertWindowsIdToIanaId", [type[]]@([string],[string].MakeByRefType()))) { if ([System.TimeZoneInfo]::TryConvertWindowsIdToIanaId($win_tz, [ref] $iana_tz)) { return $iana_tz } } # Method 2: WinRT Calendar API (Windows 10+) try { return [Windows.Globalization.Calendar,Windows.Globalization,ContentType=WindowsRuntime]::new().GetTimeZone() } catch {} # Method 3: Parse TimeZoneMapping.xml $map_path = Join-Path $Env:WinDir 'Globalization\Time Zone\TimeZoneMapping.xml' if (Test-Path $map_path) { $map_xml = [xml](Get-Content $map_path) $node = $map_xml.TimeZoneMapping.MapTZ | Where-Object { $_.WinID -eq $win_tz -and $_.Default -eq "true" } if ($node) { return $node.TZID } } # Fallback to Windows ID return $win_tz } function Get-IsoWeekDate { param ( [datetime]$date = (Get-Date) ) if ([System.Type]::GetType("System.Globalization.ISOWeek")) { $iso_week = [System.Globalization.ISOWeek]::GetWeekOfYear($date) $iso_year = [System.Globalization.ISOWeek]::GetYear($date) } else { $iso_day = (([int]$date.DayOfWeek + 6) % 7) + 1 $weekThursday = $date.AddDays(4 - $iso_day) $iso_year = $weekThursday.Year $iso_week = [System.Globalization.CultureInfo]::InvariantCulture.Calendar.GetWeekOfYear( $weekThursday, [System.Globalization.CalendarWeekRule]::FirstFourDayWeek, [System.DayOfWeek]::Monday ) } $iso_day = (([int]$date.DayOfWeek + 6) % 7) + 1 return "{0:0000}-W{1:000}-{2:000}" -f $iso_year, $iso_week, $iso_day } function Get-IsoOrdinalDate { [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] [DateTime] $Date = (Get-Date) ) process { # Format as YYYY-DDD (year and 3-digit day-of-year) $ordinal = "{0:yyyy}-{1:D3}" -f $Date, $Date.DayOfYear Write-Output $ordinal } } function Restart-FileExplorer { <# .SYNOPSIS Restarts Windows File Explorer. .DESCRIPTION Stops the 'explorer' process and starts it again. This will close and reopen the desktop, taskbar, and any open File Explorer windows. .EXAMPLE Restart-FileExplorer Restarts the File Explorer process. #> [CmdletBinding()] param () try { Write-Host "🔄 Stopping Explorer..." -ForegroundColor Yellow Stop-Process -Name explorer -Force -ErrorAction Stop Start-Sleep -Seconds 1 Write-Host "🚀 Starting Explorer..." -ForegroundColor Green Start-Process explorer.exe Write-Host "✅ Explorer restarted successfully." -ForegroundColor Cyan } catch { Write-Host "❌ Failed to restart Explorer: $_" -ForegroundColor Red } } function Get-PowerShellVersionDetails { [OutputType([pscustomobject])] param () $results = [ordered]@{} $results['PSVersion'] = $PSVersionTable.PSVersion.ToString() $results['PSEdition'] = $PSVersionTable.PSEdition $results['MajorVersion'] = $PSVersionTable.PSVersion.Major $results['ParallelSupported'] = $false $results['TernarySupported'] = $false $results['NullCoalescing'] = $false $results['PipelineChain'] = $false $results['PSStyleAvailable'] = $false $results['GetErrorAvailable'] = $false # 1. ForEach-Object -Parallel (robust test with -join) try { $output = 1..2 | ForEach-Object -Parallel { $_ * 2 } if (($output -join ',') -eq '2,4') { $results['ParallelSupported'] = $true } } catch {} # 2. Ternary operator try { $ternaryTest = Invoke-Expression '[bool]$x = $true; $x ? "yes" : "no"' if ($ternaryTest -eq 'yes') { $results['TernarySupported'] = $true } } catch {} # 3. Null-coalescing operator try { $nullCoalesce = Invoke-Expression '$null ?? "fallback"' if ($nullCoalesce -eq 'fallback') { $results['NullCoalescing'] = $true } } catch {} # 4. Pipeline chain operator (&&) try { $pipelineTest = Invoke-Expression '1..1 | ForEach-Object { "ok" } && "yes"' if ($pipelineTest -match 'yes') { $results['PipelineChain'] = $true } } catch {} # 5. $PSStyle try { if ($null -ne $PSStyle) { $results['PSStyleAvailable'] = $true } } catch {} # 6. Get-Error cmdlet try { if (Get-Command Get-Error -ErrorAction SilentlyContinue) { $results['GetErrorAvailable'] = $true } } catch {} # Final conclusion $results['Conclusion'] = if ( $results['PSEdition'] -eq 'Core' -or $results['MajorVersion'] -ge 7 -or $results['ParallelSupported'] -or $results['TernarySupported'] -or $results['NullCoalescing'] -or $results['PipelineChain'] -or $results['PSStyleAvailable'] -or $results['GetErrorAvailable'] ) { "✅ PowerShell 7+ (Core)" } elseif ( $results['PSEdition'] -eq 'Desktop' -and $results['MajorVersion'] -eq 5 ) { "🖥️ Windows PowerShell 5.1 (Desktop)" } else { "❓ Unknown or unsupported PowerShell version" } [pscustomobject]$results } function Add-ToPath { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$PathToAdd ) Write-Host "🔧 Input path: $PathToAdd" try { # Step 1: Resolve absolute path $absPath = [System.IO.Path]::GetFullPath((Resolve-Path -LiteralPath $PathToAdd).Path) if (-not (Test-Path $absPath)) { throw "❌ Path does not exist: $absPath" } # If it's a file, get its parent folder if (-not (Get-Item $absPath).PSIsContainer) { $absPath = Split-Path $absPath } $normalized = $absPath.TrimEnd('\') Write-Host "📁 Normalized path: $normalized" # Step 2: Get PATH from registry (with symbolic variables) $reg = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" ) $rawPath = $reg.GetValue("Path", "", "DoNotExpandEnvironmentNames") $reg.Close() Write-Host "📍 Current PATH (raw):" Write-Host $rawPath # Step 3: Process and expand entries # Step 3: Normalize and deduplicate $entries = $rawPath -split ';' $normalizedLower = $normalized.ToLowerInvariant() $seen = @{ } $rebuilt = @($normalized) $seen[$normalizedLower] = $true $alreadyExists = $false Write-Host "🔍 Checking each existing PATH entry against target:" foreach ($entry in $entries) { $trimmed = $entry.Trim().TrimEnd('\') if ([string]::IsNullOrWhiteSpace($trimmed)) { continue } $expanded = [Environment]::ExpandEnvironmentVariables($trimmed).TrimEnd('\') $lowerExpanded = $expanded.ToLowerInvariant() # Log only if expansion changed the string if ($trimmed -ne $expanded) { Write-Host (" - Original: {0,-70} → Expanded: {1}" -f $trimmed, $expanded) } # Detect if the normalized path already exists if ($lowerExpanded -eq $normalizedLower) { $alreadyExists = $true } # Avoid duplicates if (-not $seen.ContainsKey($lowerExpanded)) { $rebuilt += $expanded $seen[$lowerExpanded] = $true } } if ($alreadyExists) { Write-Host "✅ Path already present in system PATH (via expanded match)." return } $newPath = ($rebuilt -join ';') Write-Host "🧩 New PATH to set in registry (fully expanded):" Write-Host $newPath # Step 4: Overwrite registry with new flattened PATH Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name Path -Value $newPath Write-Host "✅ Path added to the top of system PATH." # Step 5: Broadcast environment change $signature = @" [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); "@ Add-Type -MemberDefinition $signature -Name 'Win32SendMessageTimeout' -Namespace Win32Functions $HWND_BROADCAST = [IntPtr]0xffff $WM_SETTINGCHANGE = 0x001A $SMTO_ABORTIFHUNG = 0x0002 $result = [UIntPtr]::Zero $r = [Win32Functions.Win32SendMessageTimeout]::SendMessageTimeout( $HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result ) if ($r -eq [IntPtr]::Zero) { Write-Host "⚠️ Environment change broadcast may have failed." } else { Write-Host "📢 Environment update broadcast sent." } # Step 6: Check for refreshenv and invoke if available if (Get-Command -Name refreshenv -ErrorAction SilentlyContinue) { Write-Host "♻️ Calling 'refreshenv' to update current session..." refreshenv } else { Write-Host "ℹ️ 'refreshenv' not available in this session." } } catch { Write-Error $_ } } function Remove-FromPath { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$PathToRemove ) Write-Host "🧹 Input path to remove: $PathToRemove" try { # Step 1: Resolve absolute path $absPath = [System.IO.Path]::GetFullPath((Resolve-Path -LiteralPath $PathToRemove).Path) if (-not (Test-Path $absPath)) { throw "❌ Path does not exist: $absPath" } if (-not (Get-Item $absPath).PSIsContainer) { $absPath = Split-Path $absPath } $normalized = $absPath.TrimEnd('\') $normalizedLower = $normalized.ToLowerInvariant() Write-Host "📁 Normalized path: $normalized" # Step 2: Read PATH from registry without expanding env vars $reg = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey( "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" ) $rawPath = $reg.GetValue("Path", "", "DoNotExpandEnvironmentNames") $reg.Close() Write-Host "📍 Current PATH (raw):" Write-Host $rawPath # Step 3: Split and rebuild entries (without the one we want to remove) $entries = $rawPath -split ';' $seen = @{} $rebuilt = @() $removed = $false Write-Host "🔍 Checking each PATH entry against target:" foreach ($entry in $entries) { $trimmed = $entry.Trim().TrimEnd('\') if ([string]::IsNullOrWhiteSpace($trimmed)) { continue } $expanded = [Environment]::ExpandEnvironmentVariables($trimmed).TrimEnd('\') $lowerExpanded = $expanded.ToLowerInvariant() # Log only if expansion changed the string if ($trimmed -ne $expanded) { Write-Host (" - Original: {0,-70} → Expanded: {1}" -f $trimmed, $expanded) } if ($lowerExpanded -eq $normalizedLower) { Write-Host "❌ Match found. Skipping: $expanded" $removed = $true continue } if (-not $seen.ContainsKey($lowerExpanded)) { $rebuilt += $expanded $seen[$lowerExpanded] = $true } } if (-not $removed) { Write-Host "✅ Path not found in system PATH." return } $newPath = ($rebuilt -join ';') Write-Host "🧩 New PATH to set in registry (fully expanded):" Write-Host $newPath # Step 4: Update registry Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name Path -Value $newPath Write-Host "✅ Path removed from system PATH." # Step 5: Broadcast environment change $signature = @" [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); "@ Add-Type -MemberDefinition $signature -Name 'Win32SendMessageTimeout' -Namespace Win32Functions $HWND_BROADCAST = [IntPtr]0xffff $WM_SETTINGCHANGE = 0x001A $SMTO_ABORTIFHUNG = 0x0002 $result = [UIntPtr]::Zero $r = [Win32Functions.Win32SendMessageTimeout]::SendMessageTimeout( $HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result ) if ($r -eq [IntPtr]::Zero) { Write-Host "⚠️ Environment change broadcast may have failed." } else { Write-Host "📢 Environment update broadcast sent." } # Step 6: Check for refreshenv and invoke if available if (Get-Command -Name refreshenv -ErrorAction SilentlyContinue) { Write-Host "♻️ Calling 'refreshenv' to update current session..." refreshenv } else { Write-Host "ℹ️ 'refreshenv' not available in this session." } } catch { Write-Error $_ } } function Get-FileSize { <# .SYNOPSIS Returns the total size in bytes of a file or all files within a directory. .DESCRIPTION This function accepts a path to either a file or directory. If a file, it returns its size. If a directory, it recursively computes the sum of all file sizes inside. .PARAMETER Path The path to the file or directory. .EXAMPLE Get-FileSize -Path "C:\Users\Administrator\Desktop" .OUTPUTS [Int64] The total size in bytes. #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { throw "Path '$Path' does not exist." } $item = Get-Item -LiteralPath $Path if ($item.PSIsContainer) { $size = Get-ChildItem -Path $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum return $size.Sum } else { return $item.Length } } function Get-FileSizeHumanReadable { <# .SYNOPSIS Returns the total size of a file or directory in a human-readable format with three decimal places. .DESCRIPTION This function takes a path to a file or directory. If it's a file, it reports its size. If it's a directory, it recursively sums all contained file sizes. The size is returned as a string formatted with the appropriate unit: bytes, KB, MB, GB, or TB. .PARAMETER Path The file or directory to evaluate. .EXAMPLE Get-FileSizeHumanReadable -Path "C:\Users\Administrator\Desktop" .OUTPUTS [string] A human-readable string like "123.456 MB". #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Path ) if (-not (Test-Path -LiteralPath $Path)) { throw "Path '$Path' does not exist." } $totalBytes = 0 $item = Get-Item -LiteralPath $Path if ($item.PSIsContainer) { $totalBytes = ((Get-ChildItem -Path $Path -Recurse -File -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum) ?? 0 } else { $totalBytes = $item.Length } switch ($true) { { $totalBytes -ge 1TB } { return '{0:N3} TB' -f ($totalBytes / 1TB) } { $totalBytes -ge 1GB } { return '{0:N3} GB' -f ($totalBytes / 1GB) } { $totalBytes -ge 1MB } { return '{0:N3} MB' -f ($totalBytes / 1MB) } { $totalBytes -ge 1KB } { return '{0:N3} KB' -f ($totalBytes / 1KB) } default { return "$totalBytes bytes" } } } function Bring-BackTheRightClickMenu { <# .SYNOPSIS Enables the classic Windows 10-style right-click context menu in Windows 11. .DESCRIPTION This function modifies the Windows registry to enable the classic context menu by creating a specific registry key under the current user's hive. It then restarts Windows File Explorer to apply the change. The registry path created is: HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32 This tweak is commonly used on Windows 11 systems to restore the familiar context menu behavior found in Windows 10. .EXAMPLE Bring-BackTheRightClickMenu Applies the registry tweak and restarts File Explorer. .NOTES Author: Peter Cullen Burbery Requires: Windows 11, Administrator privileges may be needed in some configurations .LINK https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry #> [CmdletBinding()] param () $registryPath = "HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" try { if (-not (Test-Path $registryPath)) { New-Item -Path $registryPath -Force | Out-Null } Set-ItemProperty -Path $registryPath -Name '(default)' -Value '' -Force Write-Host "✅ Classic right-click menu registry tweak applied." Write-Host "🔄 Restarting File Explorer..." Stop-Process -Name explorer -Force Start-Process explorer.exe Write-Host "✅ File Explorer restarted. Classic menu should be active." } catch { Write-Error "❌ Failed to apply classic menu tweak: $_" } } function Use-Windows11RightClickMenu { <# .SYNOPSIS Restores the default Windows 11-style right-click context menu. .DESCRIPTION This function deletes the registry key that forces the classic Windows 10-style context menu, restoring the default Windows 11 behavior. It removes the following registry keys: HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2} HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32 After cleaning up the registry, it restarts File Explorer so the change takes effect immediately. .EXAMPLE Use-Windows11RightClickMenu Removes the registry tweak and restarts Explorer to restore the Windows 11 menu. .NOTES Author: Peter Cullen Burbery Requires: Windows 11 Clears user-specific context menu override. .LINK https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry #> [CmdletBinding()] param () $baseKey = "HKCU:\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}" $subKey = "$baseKey\InprocServer32" try { if (Test-Path $subKey) { Remove-Item -Path $subKey -Recurse -Force Write-Host "🗑️ Removed subkey: $subKey" } if (Test-Path $baseKey) { Remove-Item -Path $baseKey -Recurse -Force Write-Host "🗑️ Removed key: $baseKey" } Write-Host "✅ Restored Windows 11 right-click menu." Write-Host "🔄 Restarting File Explorer..." Stop-Process -Name explorer -Force Start-Process explorer.exe Write-Host "✅ File Explorer restarted. Default menu should be active." } catch { Write-Error "❌ Failed to restore Windows 11 right-click menu: $_" } } function Add-DefenderExclusion { <# .SYNOPSIS Excludes a file or folder from Microsoft Defender. .DESCRIPTION If a file is provided, its parent folder will be excluded instead. Requires administrator privileges. .PARAMETER Path The absolute path to a file or folder to exclude from Defender. .EXAMPLE Add-DefenderExclusion -Path "C:\MyFolder" .EXAMPLE Add-DefenderExclusion -Path "C:\MyFolder\myfile.exe" #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string]$Path ) try { $fullPath = [System.IO.Path]::GetFullPath($Path) if (-not (Test-Path $fullPath)) { throw "❌ Path does not exist: $fullPath" } $item = Get-Item $fullPath # If it's a file, use parent directory if (-not $item.PSIsContainer) { $fullPath = $item.Directory.FullName } # Normalize: convert to full path, replace forward slashes, ensure trailing backslash $normalizedPath = ([System.IO.Path]::GetFullPath($fullPath)) -replace '/', '\' if (-not $normalizedPath.EndsWith('\')) { $normalizedPath += '\' } # Add exclusion Add-MpPreference -ExclusionPath $normalizedPath Write-Host "✅ Excluded from Microsoft Defender: $normalizedPath" } catch { Write-Error "❌ Failed to exclude from Defender: $_" } } function Get-PowershellPath { <# .SYNOPSIS Displays the current user's PATH environment variable as a formatted table with index numbers. .DESCRIPTION Splits the PATH variable by semicolon, assigns each entry a zero-padded index, and displays it in a table. .EXAMPLE Get-PowershellPath #> $i = 1 $env:Path -split ";" | ForEach-Object { [PSCustomObject]@{ Index = "{0:000}" -f $i Path = $_ } $i++ } | Format-Table -AutoSize } function Enable-LongFilePaths { <# .SYNOPSIS Enables long file path support in Windows (over 260 characters). .DESCRIPTION Modifies the registry to set LongPathsEnabled to 1. Requires admin privileges. .EXAMPLE Enable-LongFilePaths #> $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" $valueName = "LongPathsEnabled" try { # Check if running as admin if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { throw "❌ This script must be run as Administrator." } # Get current value $current = Get-ItemProperty -Path $regPath -Name $valueName -ErrorAction Stop if ($current.$valueName -eq 1) { Write-Host "ℹ️ Long file paths are already enabled (LongPathsEnabled = 1)." -ForegroundColor Yellow return } # Set value to 1 Set-ItemProperty -Path $regPath -Name $valueName -Value 1 -Type DWord Write-Host "✅ Long file paths have been enabled (LongPathsEnabled = 1)." -ForegroundColor Green } catch { Write-Error "❌ Failed to enable long file paths: $_" } } function Clean-Path { [CmdletBinding()] param () try { # Step 1: Read PATH from registry (raw, with variables) $path_key = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" $raw_path = Get-ItemPropertyValue -Path $path_key -Name Path Write-Host "📍 Current PATH (raw):" Write-Host $raw_path # Step 2: Normalize, expand, deduplicate $entries = $raw_path -split ';' $seen = @{} $rebuilt = @() Write-Host "🔍 Normalizing entries:" foreach ($entry in $entries) { $trimmed = $entry.Trim().TrimEnd('\') if ([string]::IsNullOrWhiteSpace($trimmed)) { continue } $expanded = [Environment]::ExpandEnvironmentVariables($trimmed).TrimEnd('\') $lower = $expanded.ToLowerInvariant() if ($trimmed -ne $expanded) { Write-Host (" - Original: {0,-70} → Expanded: {1}" -f $trimmed, $expanded) } if (-not $seen.ContainsKey($lower)) { $rebuilt += $expanded $seen[$lower] = $true } } $new_path = ($rebuilt -join ';') Write-Host "🧹 Cleaned PATH:" Write-Host $new_path # Step 3: Write back cleaned PATH to registry Set-ItemProperty -Path $path_key -Name Path -Value $new_path Write-Host "✅ Cleaned PATH written to registry." # Step 4: Broadcast environment change $signature = @" [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out UIntPtr lpdwResult); "@ Add-Type -MemberDefinition $signature -Name 'Win32SendMessageTimeout' -Namespace Win32Functions $HWND_BROADCAST = [IntPtr]0xffff $WM_SETTINGCHANGE = 0x001A $SMTO_ABORTIFHUNG = 0x0002 $result = [UIntPtr]::Zero $r = [Win32Functions.Win32SendMessageTimeout]::SendMessageTimeout( $HWND_BROADCAST, $WM_SETTINGCHANGE, [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result ) if ($r -eq [IntPtr]::Zero) { Write-Host "⚠️ Environment change broadcast may have failed." } else { Write-Host "📢 Environment update broadcast sent." } # Step 5: Refresh current session if possible if (Get-Command -Name refreshenv -ErrorAction SilentlyContinue) { Write-Host "♻️ Calling 'refreshenv' to update current session..." refreshenv } else { Write-Host "ℹ️ 'refreshenv' not available in this session." } } catch { Write-Error $_ } } function Get-PrimaryIPv4Address { <# .SYNOPSIS Returns the most appropriate non-virtual, connected IPv4 address. .DESCRIPTION Prefers interfaces like Wi-Fi, Ethernet, or Tailscale, and skips virtual/disconnected interfaces. .OUTPUTS [string] - IPv4 address or empty string if none found. #> try { $preferred_keywords = @("Wi-Fi", "Ethernet", "Tailscale") $excluded_keywords = @("VMware", "Virtual", "Bluetooth", "Loopback", "OpenVPN", "Disconnected") $interfaces = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.IPAddress -notlike "169.254.*" -and $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" -and $_.ValidLifetime -gt 0 } | Sort-Object -Property InterfaceMetric foreach ($preferred in $preferred_keywords) { $match = $interfaces | Where-Object { $_.InterfaceAlias -like "*$preferred*" -and ($excluded_keywords | Where-Object { $_ -in $_.InterfaceAlias }) -eq $null } | Select-Object -ExpandProperty IPAddress -First 1 if ($match) { return $match } } # Fallback to the first non-excluded interface $match = $interfaces | Where-Object { ($excluded_keywords | Where-Object { $_ -in $_.InterfaceAlias }) -eq $null } | Select-Object -ExpandProperty IPAddress -First 1 return $match } catch { return "" } } function Get-UnderscoreTimestamp { <# .SYNOPSIS Generates an underscore-delimited, timezone-aware, nanosecond-precision timestamp string. .DESCRIPTION `Get-UnderscoreTimestamp` produces a precise timestamp string in the format: YYYY_MMM_DDD_HHH_MMM_SSS_NNNNNNNNN_TimeZone_ISOYEAR_WWWW_WEEKDAY_YYYY_DOY_UnixSeconds_Nanoseconds Field breakdown: - Year (YYYY) - Month (MMM) — 3 digits by prefixing a literal 0 to a 2-digit month (e.g., January -> 001 becomes 001? actually 0MM yields 001–012; August -> 008) - Day of month (DDD) — 3 digits by prefixing a literal 0 to a 2-digit day (e.g., 009, 031) - Hour of day (HHH, 000–023) - Minute (MMM, 000–059) - Second (SSS, 000–059) - Nanoseconds (NNNNNNNNN, 9 digits) - IANA timezone identifier with slashes replaced by `_slash_` (e.g., America_slash_New_York) - ISO year (4 digits) - ISO week (Wnnn, e.g., W032) - ISO day of week (001–007, Monday = 001) - Calendar year (YYYY) - Day of year (DOY, 001–366) - Unix epoch seconds (integer) - Nanoseconds (again, 9 digits) .PARAMETER Date The date/time to format. Defaults to the current system date/time. .EXAMPLE PS> Get-UnderscoreTimestamp 2025_008_009_020_051_026_177317200_America_slash_New_York_2025_W032_006_2025_221_1754787086_177317200 .EXAMPLE PS> Get-UnderscoreTimestamp -Date (Get-Date "2025-08-08T14:23:45.1234567Z") 2025_008_008_014_023_045_123456700_Coordinated_slash_Universal_2025_W032_005_2025_221_1754663025_123456700 .NOTES - Uses `Get-IanaTimeZone` for IANA timezone resolution and replaces "/" with "_slash_". - Uses `Get-IsoWeekDate` for ISO year/week/day calculations (returned as yyyy-Wwww-ddd). - Nanoseconds are derived from .NET ticks within the second: (`$Date.Ticks % 10,000,000`) * 100. #> [CmdletBinding()] param( [datetime]$Date = (Get-Date) ) # Year $year = $Date.Year # Month (zero-padded to 3 digits by prefixing a literal 0) $month = ('0{0:D2}' -f $Date.Month) # Day of month (zero-padded to 3 digits by prefixing a literal 0) $day = ('0{0:D2}' -f $Date.Day) # Time components (zero-padded to 3) $hour = '{0:000}' -f $Date.Hour $minute = '{0:000}' -f $Date.Minute $second = '{0:000}' -f $Date.Second # Nanoseconds from ticks (ticks are 100ns) $ticksWithinSecond = $Date.Ticks % 10000000 $nanoseconds = '{0:000000000}' -f ($ticksWithinSecond * 100) # IANA timezone with _slash_ replacement $iana = Get-IanaTimeZone $tz_formatted = $iana -replace '/', '_slash_' # ISO year-week-weekday $iso = Get-IsoWeekDate -date $Date if ($iso -notmatch '^(?<y>\d{4})-W(?<w>\d{3})-(?<d>\d{3})$') { throw "Unexpected ISO week format: $iso" } $iso_year = $Matches['y'] $iso_week = $Matches['w'] $iso_dow = $Matches['d'] # Day of year (3-digit) $doy = '{0:D3}' -f $Date.DayOfYear # Unix seconds $unixSeconds = [DateTimeOffset]$Date $unixSeconds = $unixSeconds.ToUnixTimeSeconds() # Final string return "$year`_${month}`_${day}`_${hour}`_${minute}`_${second}`_${nanoseconds}`_${tz_formatted}`_${iso_year}_W${iso_week}_$iso_dow`_${year}_$doy`_${unixSeconds}_$nanoseconds" } function Get-PrimaryIPv4AddressUnderscore { <# .SYNOPSIS Returns the most appropriate non-virtual, connected IPv4 address formatted with underscores and zero-padded octets (e.g., 192_168_004_042). .DESCRIPTION Prefers interfaces like Wi-Fi, Ethernet, or Tailscale, and skips virtual/disconnected interfaces. Pads each octet to 3 digits and replaces dots with underscores. .OUTPUTS [string] - IPv4 address in underscore/zero-padded form or empty string if none found. #> try { $preferred_keywords = @("Wi-Fi", "Ethernet", "Tailscale") $excluded_keywords = @("VMware", "Virtual", "Bluetooth", "Loopback", "OpenVPN", "Disconnected") $interfaces = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction Stop | Where-Object { $_.IPAddress -notlike "169.254.*" -and $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" -and $_.ValidLifetime -gt 0 } | Sort-Object -Property InterfaceMetric $selected_ip = $null foreach ($preferred in $preferred_keywords) { $match = $interfaces | Where-Object { $_.InterfaceAlias -like "*$preferred*" -and ($excluded_keywords | Where-Object { $_ -in $_.InterfaceAlias }) -eq $null } | Select-Object -ExpandProperty IPAddress -First 1 if ($match) { $selected_ip = $match; break } } if (-not $selected_ip) { $selected_ip = $interfaces | Where-Object { ($excluded_keywords | Where-Object { $_ -in $_.InterfaceAlias }) -eq $null } | Select-Object -ExpandProperty IPAddress -First 1 } if (-not $selected_ip) { return "" } # Zero-pad and underscore-separate octets $formatted_ip = ($selected_ip -split '\.') | ForEach-Object { "{0:D3}" -f [int]$_ } return ($formatted_ip -join '_') } catch { return "" } } function Enable-SecondsInTaskbar { <# .SYNOPSIS Enables seconds display on the Windows taskbar clock. .DESCRIPTION Sets HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ShowSecondsInSystemClock to 1. Optionally restarts Explorer so the change takes effect immediately. .PARAMETER NoRestart Write the registry value but do not restart Explorer. .PARAMETER Disable Set the value to 0 (i.e., turn seconds off) instead of enabling it. .EXAMPLE Enable-SecondsInTaskbar Enables seconds and restarts Explorer. .EXAMPLE Enable-SecondsInTaskbar -NoRestart Enables seconds but does not restart Explorer. .EXAMPLE Enable-SecondsInTaskbar -Disable Disables seconds and restarts Explorer. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] param( [switch]$NoRestart, [switch]$Disable ) $regPath = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' $name = 'ShowSecondsInSystemClock' $value = if ($Disable) { 0 } else { 1 } try { # Ensure key exists if (-not (Test-Path $regPath)) { if ($PSCmdlet.ShouldProcess($regPath, 'Create registry key')) { New-Item -Path $regPath -Force | Out-Null } } $current = (Get-ItemProperty -Path $regPath -Name $name -ErrorAction SilentlyContinue).$name if ($current -ne $value) { if ($PSCmdlet.ShouldProcess("$regPath\$name", "Set to $value")) { New-ItemProperty -Path $regPath -Name $name -PropertyType DWord -Value $value -Force | Out-Null } } else { Write-Verbose "Value already set to $value." } if (-not $NoRestart) { if ($PSCmdlet.ShouldProcess('Explorer', 'Restart to apply setting')) { # Gracefully restart Explorer $explorer = Get-Process explorer -ErrorAction SilentlyContinue if ($explorer) { Stop-Process -Id $explorer.Id -Force -ErrorAction SilentlyContinue Start-Sleep -Milliseconds 500 } Start-Process explorer.exe } } else { Write-Verbose "Skipping Explorer restart due to -NoRestart." } if ($Disable) { Write-Host "Taskbar seconds have been DISABLED. $(if($NoRestart){'Restart Explorer or sign out/in to see the change.'})" } else { Write-Host "Taskbar seconds have been ENABLED. $(if($NoRestart){'Restart Explorer or sign out/in to see the change.'})" } } catch { Write-Error "Failed to update taskbar seconds: $($_.Exception.Message)" } } function Set-SystemEnvironmentVariable { <# .SYNOPSIS Creates or updates a system environment variable. .DESCRIPTION This function sets a system environment variable (persistent across sessions). Requires administrative privileges to modify system-wide variables. .PARAMETER Name The name of the environment variable. .PARAMETER Value The value to assign to the environment variable. .PARAMETER Append If specified, appends the value to the existing variable (separated by a semicolon). Useful for modifying PATH. .PARAMETER Prepend If specified, prepends the value to the existing variable (separated by a semicolon). Useful for modifying PATH. .EXAMPLE Set-SystemEnvironmentVariable -Name "MY_VAR" -Value "HelloWorld" .EXAMPLE Set-SystemEnvironmentVariable -Name "Path" -Value "C:\MyTools" -Append .EXAMPLE Set-SystemEnvironmentVariable -Name "Path" -Value "C:\MyTools" -Prepend #> [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, Position=0)] [string] $Name, [Parameter(Mandatory, Position=1)] [string] $Value, [switch] $Append, [switch] $Prepend ) begin { if ($Append -and $Prepend) { throw "You cannot use -Append and -Prepend at the same time." } $regPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" } process { try { $existing = (Get-ItemProperty -Path $regPath -Name $Name -ErrorAction SilentlyContinue).$Name if ($Append -and $existing) { if ($existing -notlike "*$Value*") { $newValue = "$existing;$Value" } else { Write-Verbose "Value already exists in $Name" $newValue = $existing } } elseif ($Prepend -and $existing) { if ($existing -notlike "*$Value*") { $newValue = "$Value;$existing" } else { Write-Verbose "Value already exists in $Name" $newValue = $existing } } else { $newValue = $Value } if ($PSCmdlet.ShouldProcess("System Environment Variable [$Name]", "Set to [$newValue]")) { Set-ItemProperty -Path $regPath -Name $Name -Value $newValue # Broadcast WM_SETTINGCHANGE to update environment for other processes $signature = @" [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern IntPtr SendMessageTimeout( IntPtr hWnd, int Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult); "@ Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition $signature -ErrorAction SilentlyContinue $HWND_BROADCAST = [intptr]0xffff $WM_SETTINGCHANGE = 0x1A $result = [intptr]::Zero [Win32.NativeMethods]::SendMessageTimeout( $HWND_BROADCAST, $WM_SETTINGCHANGE, [intptr]::Zero, "Environment", 2, 5000, [ref] $result ) | Out-Null Write-Output "System environment variable [$Name] set to [$newValue]." } } catch { Write-Error "Failed to set system environment variable: $_" } } } |