Taskbar Toolmaking  in PowerShell

Taskbar Toolmaking in PowerShell

ยท

6 min read

Application icons on the Windows Taskbar have some useful features such as an overlay badge, thumb buttons and a progress indicator. Since they are easy to access as a UI and they can also notify the users of some information, I thought it would be fantastic if we could easily use them from PowerShell. This was the reason why I made the PoshTaskbarItem module and in this article, I would like to show some examples of what is possible with this module.

Module Usage

Install the module with this command:

Install-Module -Name PoshTaskbarItem -Scope CurrentUser

then, you can create a clickable taskbar icon with this one-liner:

New-TaskbarItem -OnClicked {Write-Host 'Hi'} | Show-TaskbarItem

The ScriptBlock passed by the OnClicked parameter runs when the icon is clicked.

Another typical task of the taskbar tools is executing a ScriptBlock periodically at a certain interval and notifying the user of some information with an overlay badge or a progress indicator. This can be achieved by the following code:

# Counter.ps1
$onClicked = {
    Write-Host 'Hi'
}

$count = 0
$onUpdate = {
    $script:count = ($script:count + 1) % 10
    $progress = $script:count / 10
    $script:ti | Set-TaskbarItemOverlayBadge -Text $count
    $script:ti | Set-TaskbarItemProgressIndicator -Progress $progress -State Normal
}

$ti = New-TaskbarItem -Title 'Counter' -OnClicked $onClicked
$ti | Set-TaskbarItemTimerFunction -IntervalInMillisecond 1000 -ScriptBlock $onUpdate
$ti | Show-TaskbarItem

Finally, we create a shortcut to assign an app icon to the script. You can use existing icon resources stored in dll and exe, or image files such as png.

$params = @{
    Path = "$env:userprofile\Desktop\Counter.lnk"
    IconResourcePath = 'imageres.dll'
    IconResourceIndex = 204
    TargetPath = 'pwsh.exe'
    Arguments = '-ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -File "{0}"' -f 'D:\Counter.ps1'
    WindowStyle = 'Minimized'
}
New-TaskbarItemShortcut @params

This is the common structure of the tools we make in this article. We'll mainly customize $onClicked and $onUpdate ScriptBlocks for each tool. Let's see more practical examples from here!

๐Ÿ“‚ Folder Cleaner

If you are working in an environment with small storage, you might want to monitor the size of some folders, e.g. TEMP or Downloads, and want a quick way to delete the files under that folder. It could be nice if we have a shortcut on the taskbar with the size notification.

The size monitoring is pretty easy. We can calculate the size of the folder and show it as a description text. The progress indicator shows the percentage with an 80% color alert.

# Monitors Downloads folder as an example
$folderPath = "$env:userprofile\Downloads"
$sizeLimitMb = 64

$onUpdate = {
    $totalSize = (Get-ChildItem $script:folderPath -Recurse | Measure-Object -Property Length -Sum).Sum
    $totalSizeMb = [Int]($totalSize / 1MB)
    $desc = '{0} / {1} MB' -f $totalSizeMb, $script:sizeLimitMb

    $script:ti | Set-TaskbarItemDescription -Description $desc

    $progress = [Math]::Min($totalSizeMb / $script:sizeLimitMb, 1.0)
    $progressState = 'Normal'
    if ($progress -gt 0.8) {
        $progressState = 'Error'
    }

    $script:ti | Set-TaskbarItemProgressIndicator -Progress $progress -State $progressState
}

Let's add a thumb button to delete the files. For a better UI response, OnClicked ScriptBlock needs to return quickly so we use a ThreadJob for the message box and the actual delete operation.

$emptyFolder = {
    param($folderPath)
    $okCancel = [System.Windows.Forms.MessageBox]::Show(
        "Empty folder [$folderPath]?", 
        'Folder Cleaner', 
        'OKCancel')
    if ($okCancel -eq 'OK') {
        Remove-Item "$folderPath\*" -Force
    }
}

$params = @{
    Description = 'Empty Folder'
    IconResourcePath = 'imageres.dll'
    IconResourceIndex = 233
    OnClicked = {
        Start-ThreadJob -ScriptBlock $emptyFolder -ArgumentList $script:folderPath
    }
}
$thumbButton = New-TaskbarItemThumbButton @params
$ti | Add-TaskbarItemThumbButton -ThumbButton $thumbButton

Finally, clicking the icon opens the folder.

$onClicked = {
    & explorer $script:folderPath
}

๐Ÿ“ˆ Taskmgr Shortcut

Let's say you have to work with a machine that has a small amount of memory. You should be more interested in the memory size that each app consumes and should want to open the Task Manager quickly in case you have to stop some processes.

The percentage of memory usage can be shown as an overlay badge. It should be also helpful if we can see the top 5 process names when we hover over the icon.

$onUpdate = {
    $os = Get-CimInstance Win32_OperatingSystem
    [Int]$usage = 100 * ($os.TotalVisibleMemorySize - $os.FreePhysicalMemory) / $os.TotalVisibleMemorySize

    $groups = Get-Process | Group-Object Name | ForEach-Object {
        $workingSetSum = 0
        foreach ($process in $_.Group) {
            $workingSetSum += $process.WorkingSet
        }
        @{
            Name = $_.Name
            WorkingSetSum = $workingSetSum
        }
    }

    $groups = $groups | Sort-Object -Property WorkingSetSum -Descending
    $top5 = $groups[0..4]    
    $description = "--- Memory Top 5 ---`n"
    $description += $top5.Name -join "`n"

    $color = 'LightSeaGreen'
    if ($usage -ge 50) {
        $color = 'DeepPink'
    }

    $script:ti | Set-TaskbarItemOverlayBadge -Text $usage -FontSize 10 -BackgroundColor $color
    $script:ti | Set-TaskbarItemDescription -Description $description
}

$onClicked simply opens the Task Manager:

$onClicked = {
    Start-Process 'Taskmgr.exe'
}

๐Ÿ”” GitHub Notifier

GitHub has a nice notifications page where you can manage your notifications. However, as far as I know, there is no push notification support at this time. You could use email notifications but a dedicated icon and notifications on the taskbar might be more useful.

First, you need to get your unread notifications using the GitHub notifications API. Once you've created a personal access token with the notifications scope, you can just send a GET method with Invoke-WebRequest. Refer to the API documentation for more information about the token and the API.

This code gets the unread notifications and shows their titles in the description text. The unread count is displayed as an overlay badge. I used SecureString to store my personal access token locally but if you are not familiar with SecureString, I recommend that you read this blog post.

$githubToken = Get-Content "$env:userprofile\github_token.txt" | ConvertTo-SecureString | ConvertFrom-SecureString -AsPlainText

$onUpdate = {
    $headers = @{
        'Accept' = 'application/vnd.github+json'
        'Authorization' = "Bearer $githubToken"
        'X-GitHub-Api-Version' = '2022-11-28'
    }
    $response = Invoke-WebRequest -Uri 'https://api.github.com/notifications' -Method GET -Headers $headers
    if ($response.StatusCode -ne 200) {
        # Status is not OK
        return
    }

    $unreadNotifications = @($response.Content | ConvertFrom-Json)
    $description = ''
    foreach ($notification in $unreadNotifications) {
        $description += $notification.subject.title + "`n"
    }
    $script:ti | Set-TaskbarItemDescription -Description $description

    $unreadCount = $unreadNotifications.Count
    if ($unreadCount) {
        $script:ti | Set-TaskbarItemOverlayBadge -Text $unreadCount -BackgroundColor DodgerBlue
    } else {
        $script:ti | Clear-TaskbarItemOverlay
    }
}

In the web response, there is an X-Poll-Interval header that specifies how often in seconds you are allowed to poll. Let's follow this poll interval and show the waiting time as a progress bar.

$pollInterval = 60
$lastPollDate = [DateTime]::MinValue
$onUpdate = {
    $elapsedSeconds = ((Get-Date) - $script:lastPollDate).TotalSeconds
    $progress = [Math]::Min($elapsedSeconds / $pollInterval, 1.0)
    $ti | Set-TaskbarItemProgressIndicator -Progress $progress -State Normal
    if ($elapsedSeconds -lt $pollInterval) {
        return
    }

    # The previous code to get notifications here...

    $newPollInterval = [Int]($response.Headers.'X-Poll-Interval'[0])
    if ($newPollInterval) {
        $script:pollInterval = $newPollInterval
    }
    $script:lastPollDate = Get-Date
}

When you click the icon, it opens the GitHub notifications page.

$onClicked = {
    & explorer 'https://github.com/notifications'
}

โŒ› Work Time Tracker

This is a tool I personally use. It tracks your work time and notifies you of overtime. I created this tool so that it can be used as a template project and all the code is available on the GitHub repo. The repository includes the PoshTaskbarItem module as a git submodule. It also includes a GitHub action to make a zip file as a release package so you can refer to it if you would like to distribute the tool to the users.

Conclusion

With PoshTaskbarItem module, you can quickly create simple taskbar tools in PowerShell. If you use PowerShell daily, I guess you already have 1 or 2 scripts that are good to stay on the taskbar.

I hope you come up with a cool tool that helps you improve your daily process. Enjoy toolmaking!

References

ย