Skip to content

Instantly share code, notes, and snippets.

@indented-automation
Created July 22, 2022 10:36
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indented-automation/8035cdbe21ff38ab8eb79bb4be84d2e6 to your computer and use it in GitHub Desktop.
Save indented-automation/8035cdbe21ff38ab8eb79bb4be84d2e6 to your computer and use it in GitHub Desktop.
Functions to allow changes to the visibility of icons in the system tray.
enum Visibility : byte {
Default = 0
Hide = 1
Show = 2
}
function Convert-CeaserCipher {
<#
.SYNOPSIS
Convert a string to and from a ceaser cipher (ROT-13) encoding.
.DESCRIPTION
Convert a string to and from a ceaser cipher (ROT-13) encoding.
#>
[CmdletBinding()]
param (
# The string to encode or decode.
[Parameter(Mandatory, ValueFromPipeline)]
[string]$String
)
process {
$chars = foreach ($char in $string.ToCharArray()) {
if ($char -notmatch '[a-z]') {
$char
continue
}
$decrement = ($char -ge 'A' -and $char -le 'Z' -and $char -gt 'M') -or
($char -ge 'a' -and $char -le 'z' -and $char -gt 'm')
if ($decrement) {
[int]$char - 13
} else {
[int]$char + 13
}
}
[string]::new([char[]]$chars)
}
}
function Resolve-KnownFolder {
<#
.SYNOPSIS
Resolve GUID known folder values to full paths.
.DESCRIPTION
Resolve GUID known folder values to full paths.
#>
[CmdletBinding()]
param (
# A path containing a GUID to resolve.
[Parameter(ValueFromPipeline)]
[string]$Path
)
begin {
if (-not ('KnownFolder' -as [Type])) {
Add-Type -TypeDefinition '
using System;
using System.Runtime.InteropServices;
internal class UnsafeNativeMethods
{
[DllImport("shell32.dll")]
internal static extern int SHGetKnownFolderPath(
[MarshalAs(UnmanagedType.LPStruct)] Guid rfid,
uint dwFlags,
IntPtr hToken,
out IntPtr ppszPath
);
}
public class KnownFolder {
public static string GetPath(Guid guid)
{
IntPtr ppszPath = IntPtr.Zero;
UnsafeNativeMethods.SHGetKnownFolderPath(
guid,
0,
IntPtr.Zero,
out ppszPath
);
string path = Marshal.PtrToStringUni(ppszPath);
Marshal.FreeCoTaskMem(ppszPath);
return path;
}
}
'
}
}
process {
$pathElements = $Path -split '[\\/]'
if ($guid = $pathElements[0] -as [Guid]) {
$pathElements[0] = [KnownFolder]::GetPath($guid)
$Path = [System.IO.Path]::Combine($pathElements)
}
$Path
}
}
function Get-SystemTrayIcon {
<#
.SYNOPSIS
Get system tray icon visibility.
.DESCRIPTION
Get system tray icon visibility using the IconStreams value in the registry.
#>
[CmdletBinding()]
param (
# The path for the system tray icon. By default app paths are displayed.
[string]$Path = '*'
)
$registryPath = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify'
$iconStreams = Get-ItemPropertyValue -Path $registryPath -Name IconStreams
# The flag for visibility appears at offset 528
$systemTrayToggleOffset = 528
# The iconStreams array has a 20 byte header, followed by 1640 byte records describing each icon.
for ($i = 20; $i -lt $iconStreams.Count; $i += 1640) {
# The path is Unicode encoded and null terminated.
$iconPathBytes = for (($j = 0), ($isChar = $true); $isChar; $j += 2) {
$left = $iconStreams[$i + $j]
$right = $iconStreams[$i + $j + 1]
$isChar = $left -ne 0 -or $right -ne 0
if ($isChar) {
$left, $right
}
}
$iconPath = [System.Text.Encoding]::Unicode.GetString([byte[]]$iconPathBytes) | Convert-CeaserCipher
if ($iconPath -like $Path) {
[PSCustomObject]@{
PSTypeName = 'IconStreamsRecord'
Visibility = [Visibility]$iconStreams[$i + $systemTrayToggleOffset]
Offset = $i
Path = Resolve-KnownFolder $iconPath
}
}
}
}
function Backup-SystemTrayIcon {
<#
.SYNOPSIS
Create a backup of the System Tray Icon configuration (IconStreams).
.DESCRIPTION
Create a backup of the System Tray Icon configuration (IconStreams).
#>
[CmdletBinding()]
param ( )
$registryPath = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify'
$value = Get-ItemPropertyValue $registryPath -Name IconStreams
New-ItemProperty -Path $registryPath -Name IconStreams_Backup -Value $value -Force
}
function Restore-SystemTrayIcon {
<#
.SYNOPSIS
Restore an existing backup of the System Tray Icon configuration (IconStreams).
.DESCRIPTION
Restore an existing backup of the System Tray Icon configuration (IconStreams).
#>
[CmdletBinding()]
param ( )
$registryPath = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify'
$value = Get-ItemPropertyValue $registryPath -Name IconStreams_Backup
if ($value) {
Set-ItemProperty -Path $registryPath -Name IconStreams -Value $value
} else {
Write-Error 'IconStreams backup does not exist!'
}
}
function Set-SystemTrayIcon {
<#
.SYNOPSIS
Set system tray icon visibility.
.DESCRIPTION
Set system tray icon visibility using the IconStreams value in the registry.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory, ParameterSetName = 'ByPath')]
[SupportsWildcards()]
[string]$Path,
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'FromPipeline')]
[PSTypeName('IconStreamsRecord')]
[object]$InputObject,
[Parameter(Mandatory)]
[Visibility]$Visibility
)
begin {
$registryPath = 'HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify'
$systemTrayToggleOffset = 528
if ($PSCmdlet.ParameterSetName -eq 'Path') {
Get-SystemTrayIcon -Path $Path | Set-SystemTrayIcon -Visibility $Visibility
return
}
$iconStreams = Get-ItemPropertyValue -Path $registryPath -Name IconStreams
$shouldUpdate = $false
}
process {
if ($PSCmdlet.ParameterSetName -eq 'ByPath') {
return
}
if ($InputObject.Visibility -ne $Visibility) {
$shouldUpdate = $true
$iconStreams[$InputObject.Offset + $systemTrayToggleOffset] = $Visibility.value__
}
}
end {
if ($PSCmdlet.ParameterSetName -eq 'ByPath') {
return
}
if ($shouldUpdate) {
Set-ItemProperty -Path $registryPath -Name IconStreams -Value $iconStreams
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment