Clinic IT QuickCheck Toolbar

Build a responsive Windows toolbar for safe clinic IT diagnostics.

Introduction

30 Second Summary

A printer stops during a busy clinic shift. Finding the cause can mean checking several disconnected parts of the workstation.

In this project, you will combine Batch, PowerShell, and Python with Tkinter to build a floating Windows toolbar for authorized clinic IT triage. Each click launches a focused check that returns a timestamped local report without freezing the interface.

What You'll Build

One click on the right-edge toolbar produces an on-screen diagnostic report with a timestamped copy in the local logs folder.

By the end of this project, you'll have:

  • A one-click network snapshot showing the workstation hostname plus its complete TCP/IP configuration. Role-based ICMP status shows whether each authorized clinic device returns a reply from its configured address.
  • Printer and workstation health reports showing the local Print Spooler, current print jobs, disk capacity, last boot time, and recent system errors. A guarded preview shows the effect of a local spooler restart before any service changes.
  • A right-edge toolbar that stays responsive while checks run. It displays completed reports and opens timestamped files from the local logs folder.
  • Secret Mission: An All Safe Checks button that runs the four read-only diagnostics one at a time. It saves four reports while excluding the Print Spooler repair.

Are there any prerequisites?

You need a Windows computer with PowerShell, Python, and Visual Studio Code already installed. The project also assumes access to authorized clinic device addresses.

Administrator rights are needed only for the optional Print Spooler restart.

Before We Start

Before any checks touch the clinic network, this checkpoint defines the tool's narrow purpose. That purpose is a clinic IT toolbar built around read-only diagnostics for faster network, printer, and workstation triage without controlling clinical devices.

Set Up the Project and Safety Checks

A predictable folder structure keeps path mistakes from looking like script bugs. Runtime checks expose missing modules before later scripts depend on them.

You will create a shared workspace in Visual Studio Code.

You will verify Python 3 with Tkinter support. You will also confirm Windows PowerShell 5.1.

In this step, get ready to:
  • Create the clinic-it-quickcheck workspace with its three subfolders.
  • Verify Python 3 with Tkinter support.
  • Confirm the PowerShell safety prerequisites for script policy and printer diagnostics.
Create the project workspace

Every script in this project uses paths relative to one shared folder. The three subfolders separate scripts from configuration files and generated reports.

  • Press the Windows key to open the search bar.
  • Type Visual Studio Code and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder....
  • Navigate to your Desktop in the folder dialog.
  • Select New Folder.
  • Type clinic-it-quickcheck and press Enter.

The clinic-it-quickcheck folder now exists on your Desktop.

  • Select the clinic-it-quickcheck folder.
  • Click Select Folder.
  • Review the folder name if the Workspace Trust dialog appears.
  • Select Yes, I trust the authors because you created this empty folder.

You should see clinic-it-quickcheck at the top of the Explorer sidebar.

  • Use the New Folder control in the Explorer toolbar to create scripts.
  • Use the same control to create config.

Confirm that both new subfolders appear beneath clinic-it-quickcheck.

  • Use the New Folder control to create logs.

Good progress. Your workspace now keeps scripts, configuration, and local reports in separate locations.

Folder missing from the Explorer?

Confirm that the Explorer heading shows clinic-it-quickcheck. Reopen that folder from your Desktop if another workspace is shown.

Help me fix my Visual Studio Code project folder structure.

Verify the local runtimes

The toolbar depends on the installed Python interpreter. Its interface also depends on Tkinter being available in that interpreter.

  • Click View in the Visual Studio Code menu bar.
  • Click Terminal.

You should see the integrated terminal open at the clinic-it-quickcheck workspace.

  • Check the installed Python version by running:
python --version

What does this check do?

The python --version command prints the version used by the terminal. This confirms which interpreter later launches the toolbar.

You should see Python followed by a version beginning with 3.

Python unavailable in the terminal?

The installed Python launcher may be missing from this terminal session. Confirm your clinic workstation uses its approved Python installation before continuing.

Help me make my installed Python available in the Visual Studio Code terminal.

The next check launches a small demonstration window. Closing that window returns control to the terminal.

  • Check Tkinter by running:
python -m tkinter

What does the Tkinter check prove?

The python -m tkinter command loads the graphical toolkit from your active Python installation. A demonstration window confirms that the toolbar can create its interface.

You should see a small Tk demo window. It shows the installed Tcl/Tk version.

  • Close the Tk demo window to return to the terminal.

Tk demo window missing?

The active Python installation may not include Tkinter support. Resolve that runtime issue with your clinic IT team before building the toolbar.

Help me diagnose why the Tkinter demonstration window does not open.

The diagnostic scripts target Windows PowerShell 5.1. The next command asks that executable to report its own version.

  • Check the Windows PowerShell version by running:
powershell.exe -NoProfile -Command "$PSVersionTable.PSVersion"

Why use a clean PowerShell session?

The command starts powershell.exe without loading a user profile. It then prints the version reported by that Windows PowerShell session.

The Major value should be 5. The Minor value should be 1.

Seeing a different PowerShell version?

  • Stop before creating any PowerShell scripts.
  • Ask your clinic IT team to confirm the approved Windows PowerShell 5.1 installation.

Help me understand the PowerShell version shown by this check.

Review PowerShell safety prerequisites

An execution policy controls when PowerShell permits scripts to run under each scope. This project inspects the current policy without changing it.

  • Inspect every execution-policy scope by running:
Get-ExecutionPolicy -List

What does this policy check show?

The Get-ExecutionPolicy -List command displays the policy configured for each scope. It does not modify those settings.

You should see a table of policy scopes with their current values.

Keep clinic policy intact

Organizational Group Policy can override local execution-policy choices. This project does not add an execution-policy bypass.

  • Follow your clinic's approved process if scripts are restricted.
  • Leave the execution policy unchanged in this project.

The printer diagnostics rely on the PrintManagement commands for printer inventory and queued jobs. Both commands must be available before you build those checks.

Before you run this check, do you expect PowerShell to find both printer commands on this workstation?

  • Check for both PrintManagement commands by running:
Get-Command -Name Get-Printer,Get-PrintJob

What does command discovery prove?

The Get-Command -Name Get-Printer,Get-PrintJob check asks PowerShell to find both required commands. Returned command records confirm that later printer diagnostics can call them.

The Name column should list Get-Printer. It should also list Get-PrintJob.

One of the printer commands missing?

  • Stop before building the printer health script.
  • Ask your clinic IT team to make the approved PrintManagement commands available on this workstation.

Help me diagnose a missing PrintManagement command.

Setup complete. Your workspace is organized and every required local runtime check now passes.

Your project foundation is ready. Next up, you will build the first read-only network snapshot.

Build the Batch Network Snapshot

Your local runtimes are verified, so the project now has a safe foundation. A read-only Batch snapshot is the first useful diagnostic for the workstation.

A workstation snapshot can quickly reveal the local network state. The key question is whether that local view can also identify which clinic endpoint is responding.

In this step, get ready to:
  • Create a labeled Batch script for local network details.
  • Run the snapshot from the clinic-it-quickcheck folder.
  • Identify the active network path from the output.
Lay out the snapshot sequence

Batch files can run several built-in Windows commands under clear labels. This gives you one repeatable snapshot without changing network settings.

  • Select the scripts folder in the left file tree in Visual Studio Code.
  • Use the new-file control above the file tree to create Quick-Network-Check.bat inside scripts.
  • Paste the following code into scripts\Quick-Network-Check.bat:
@echo off
setlocal

echo Clinic IT Quick Network Check
echo.
echo ==== COMPUTER NAME ====
hostname

echo.
echo ==== TCP/IP CONFIGURATION ====
ipconfig /all

echo.
echo ==== ARP CACHE ====
arp -a

endlocal

What Does This Code Do?

  • The @echo off line keeps the Command Prompt output focused on diagnostic results.
  • The setlocal and endlocal lines contain any environment changes within this script.
  • The echo lines divide the output into sections that are easier to scan.
  • The hostname command displays the workstation name.
  • The ipconfig /all command displays the full TCP/IP configuration for every adapter.
  • The arp -a command displays the current ARP cache.
  • Save scripts\Quick-Network-Check.bat.
  • Confirm the left file tree lists Quick-Network-Check.bat inside scripts.

File Missing From Scripts?

Check that the file is inside the existing scripts folder. Confirm its name ends with .bat.

If the file appears as a text document, rename it to Quick-Network-Check.bat.

Help me check why my Batch file is missing or has the wrong extension.

✔️ Awesome, I've got everything!

  • Confirm that scripts\Quick-Network-Check.bat is saved.

ⓧ I'd like to double check the full code

The complete file should match this reference exactly.

@echo off
setlocal

echo Clinic IT Quick Network Check
echo.
echo ==== COMPUTER NAME ====
hostname

echo.
echo ==== TCP/IP CONFIGURATION ====
ipconfig /all

echo.
echo ==== ARP CACHE ====
arp -a

endlocal

How Is the File Organized?

The file prints one heading before each read-only command. The blank lines keep the computer name, TCP/IP configuration, and ARP cache visually separate.

Inspect the workstation snapshot

Each section answers a question about the local workstation. Running the complete script also reveals the boundary of this diagnostic.

Before you run it, predict whether the snapshot can identify a responding clinic endpoint.

  • Switch back to the Visual Studio Code terminal from the setup step.
  • Run the Batch network snapshot from the clinic-it-quickcheck folder with this command:
scripts\Quick-Network-Check.bat

What Does the Snapshot Reveal?

  • The COMPUTER NAME section identifies the local workstation.
  • The TCP/IP CONFIGURATION section shows the full configuration for each adapter.
  • The ARP CACHE section lists neighboring network entries already known by the workstation.
  • No section tests the authorized clinic endpoints individually.
  • Locate the adapter section that contains an assigned network address.
  • Identify the default gateway in that adapter section.
  • Identify the DNS server entries in that adapter section.
  • Review the ARP cache rows for cached neighboring addresses.
  • Check the snapshot for a role-based clinic device status.

You should now be able to name the active adapter, default gateway, DNS servers, and cached neighbors. You will not see a per-device clinic status anywhere in the snapshot.

Snapshot Did Not Run?

Confirm the terminal path ends with clinic-it-quickcheck. The script path is relative to that folder.

Check that Quick-Network-Check.bat is saved inside scripts.

Help me troubleshoot my Batch network snapshot.

That is your first clinic diagnostic running from end to end. Next, you will connect network results to authorized clinic device roles without controlling those devices.

Add Configurable Device Checks

Your Batch snapshot now exposes the workstation's network context. The next goal is to match authorized clinic roles with an observable reachability result.

A configuration-driven PowerShell check keeps target addresses outside the script. A missing ICMP reply stays inconclusive because network controls can block the request.

In this step, get ready to:
  • Create a role-based CSV target list with authorized addresses.
  • Build a PowerShell check that returns one status per row.
  • Run the check to interpret missing ICMP replies safely.
Create the authorized target list

A CSV file gives each authorized address a role-based name. This keeps the script reusable when clinic equipment changes.

  • Select the config folder in Visual Studio Code's file sidebar.
  • Use the new-file icon at the top of the file sidebar.
  • Type targets.csv in the file name field.
  • Press Enter to create the file.
  • Add the role-based template to config\targets.csv by copying this content:
Name,Address,Category
Clinic gateway,REPLACE_ME,Network
Shared printer,REPLACE_ME,Printer
Front desk workstation,REPLACE_ME,Workstation
Clinical workstation,REPLACE_ME,Workstation
Wi-Fi EKG,REPLACE_ME,Medical device

How is the target list structured?

  • The Name column uses role-based labels that remain useful when hardware changes.
  • The Address column holds an authorized clinic device address or REPLACE_ME.
  • The Category column groups network equipment, printers, workstations, and medical devices.
  • Replace each REPLACE_ME entry for which you have an authorized clinic address.
  • Leave REPLACE_ME in any row without an approved address.
  • Remove any patient information from the file.
  • Save config\targets.csv.
  • Confirm the header contains the three columns Name, Address, and Category.
  • Confirm five role-based target rows appear below the header.

Good progress. The target list now separates clinic roles from the diagnostic logic.

Target list not lining up?

Check that every row has exactly three comma-separated values. Remove extra commas from role names because each comma creates another column.

Ask for help checking the CSV structure.

✔️ Awesome, I've got everything!

Your target list is saved. Authorized addresses are the only expected differences from the template.

ⓧ I'd like to double check the full code

Compare config\targets.csv with this complete template. Keep any authorized addresses you already added.

Name,Address,Category
Clinic gateway,REPLACE_ME,Network
Shared printer,REPLACE_ME,Printer
Front desk workstation,REPLACE_ME,Workstation
Clinical workstation,REPLACE_ME,Workstation
Wi-Fi EKG,REPLACE_ME,Medical device
Build the ICMP status script

The script first locates the target list relative to its own file. This avoids relying on whichever directory the terminal currently uses.

  • Select the scripts folder in Visual Studio Code's file sidebar.
  • Use the new-file icon at the top of the file sidebar.
  • Type Get-ClinicDeviceStatus.ps1 in the file name field.
  • Press Enter to create the file.
  • Add the target-list loading logic to scripts\Get-ClinicDeviceStatus.ps1 by copying this code:
$targetsPath = "$PSScriptRoot\..\config\targets.csv"

try {
    $targets = Import-Csv -LiteralPath $targetsPath -ErrorAction Stop
}
catch {
    Write-Error "Could not read the target list at $targetsPath. $($_.Exception.Message)"
    exit 1
}

How does the script find the CSV?

  • The $PSScriptRoot value points to the folder containing the running script.
  • The $targetsPath value moves up to clinic-it-quickcheck before entering the config folder.
  • The Import-Csv command converts each CSV row into an object with named properties.
  • The error handler stops the check when the target list cannot be read.

The result loop has two paths. Placeholder rows get a safe status while configured rows receive one ICMP request.

  • Build the first part of the foreach loop by pasting this below the catch block:
$results = foreach ($target in $targets) {
    if (-not $target.Address -or $target.Address -eq 'REPLACE_ME') {
        [PSCustomObject]@{
            Name         = $target.Name
            Category     = $target.Category
            Address      = $target.Address
            Reachability = 'Not configured'
        }
        continue
    }

    $responding = Test-Connection `
        -ComputerName $target.Address `
        -Count 1 `
        -Quiet `
        -ErrorAction SilentlyContinue

    $reachability = if ($responding) {
        'ICMP reply received'
    }
    else {
        'No ICMP reply'
    }

How does each target get a status?

  • The placeholder branch returns Not configured without sending a request.
  • The Test-Connection command sends one ICMP echo request to a configured address.
  • The -Quiet option stores a Boolean result in $responding.
  • The outer foreach block stays open at the end of this snippet. The next snippet closes it before you save the file.
  • Complete the foreach loop by pasting this directly below the previous code:
    [PSCustomObject]@{
        Name         = $target.Name
        Category     = $target.Category
        Address      = $target.Address
        Reachability = $reachability
    }
}

What does each result contain?

Each result object keeps the role name, category, address, and cautious reachability label together. The closing brace finishes the outer loop.

  • Add the final report output by pasting this below the completed foreach loop:
Write-Output 'Clinic Device Reachability'
Write-Output 'A missing ICMP reply does not prove that a device is offline.'
$results | Format-Table -AutoSize | Out-String -Width 200

How is the report presented?

The report opens with a clear heading. It also prints the safety reminder before formatting every result as a readable table.

  • Save scripts\Get-ClinicDeviceStatus.ps1.
  • Confirm the file ends with the table-formatting pipeline.

Seeing mismatched braces or quotes?

Confirm the second loop snippet sits directly below the first one. Check that every opening brace has a closing brace.

Ask for help comparing the PowerShell structure.

✔️ Awesome, I've got everything!

The device-status script is complete. Double check that scripts\Get-ClinicDeviceStatus.ps1 is saved.

ⓧ I'd like to double check the full code

Compare your complete scripts\Get-ClinicDeviceStatus.ps1 file with this version.

$targetsPath = "$PSScriptRoot\..\config\targets.csv"

try {
    $targets = Import-Csv -LiteralPath $targetsPath -ErrorAction Stop
}
catch {
    Write-Error "Could not read the target list at $targetsPath. $($_.Exception.Message)"
    exit 1
}

$results = foreach ($target in $targets) {
    if (-not $target.Address -or $target.Address -eq 'REPLACE_ME') {
        [PSCustomObject]@{
            Name         = $target.Name
            Category     = $target.Category
            Address      = $target.Address
            Reachability = 'Not configured'
        }
        continue
    }

    $responding = Test-Connection `
        -ComputerName $target.Address `
        -Count 1 `
        -Quiet `
        -ErrorAction SilentlyContinue

    $reachability = if ($responding) {
        'ICMP reply received'
    }
    else {
        'No ICMP reply'
    }

    [PSCustomObject]@{
        Name         = $target.Name
        Category     = $target.Category
        Address      = $target.Address
        Reachability = $reachability
    }
}

Write-Output 'Clinic Device Reachability'
Write-Output 'A missing ICMP reply does not prove that a device is offline.'
$results | Format-Table -AutoSize | Out-String -Width 200
Run and interpret the device check

The table distinguishes configuration gaps from observed network behavior. A silent device can still be running when ICMP traffic is filtered.

Before you run the check, pause to consider which rows might return each of the three statuses.

  • Run the device check from the clinic-it-quickcheck terminal by entering this command:
powershell.exe -NoProfile -File .\scripts\Get-ClinicDeviceStatus.ps1

How should you read the result?

  • The ICMP reply received status confirms that the target answered this request.
  • The No ICMP reply status records an absent response without declaring the device offline.
  • The Not configured status identifies a row whose address still contains REPLACE_ME.

You'll see Clinic Device Reachability followed by one table row for each clinic role.

Each row ends with ICMP reply received, No ICMP reply, or Not configured.

You now have the crucial distinction between a confirmed reply and an inconclusive missing reply.

No table or missing rows?

  • Confirm the terminal is using the clinic-it-quickcheck folder.
  • Check that config\targets.csv still has the Name, Address, and Category headers.
  • Check that the script file is named Get-ClinicDeviceStatus.ps1 inside the scripts folder.

Ask for help diagnosing the device check.

Your clinic roles now have reusable reachability checks without controlling any device. Next, you'll add printer and workstation diagnostics with a guarded Print Spooler preview.

Add Printer and Workstation Health Checks

You already turned role-based clinic addresses into cautious ICMP reachability results. That check cannot explain a stalled printer or an unhealthy workstation.

This step adds focused PowerShell reports for printer state. It also adds local workstation health data. The only state-changing action stays in a separate previewable script.

In this step, get ready to:
  • Report the local Print Spooler status plus installed printers and queued jobs.
  • Report operating system details plus boot time, disk capacity, and recent serious events.
  • Build a guarded Print Spooler restart script and test only its preview path.
Inspect printer and queue health

The Print Spooler manages local printing. The PrintManagement commands expose installed printers and queued jobs without changing either one.

  • Use the new-file control in the Visual Studio Code file sidebar to create scripts\Get-PrinterHealth.ps1.

You should see Get-PrinterHealth.ps1 listed inside the existing scripts folder.

  • Paste the first printer inventory block into scripts\Get-PrinterHealth.ps1 using the code below:
Write-Output 'Print Spooler'
Get-Service -Name Spooler |
    Format-Table -AutoSize |
    Out-String -Width 200

try {
    $printers = @(Get-Printer -ErrorAction Stop)
}
catch {
    Write-Error "Printer inventory failed. $($_.Exception.Message)"
    exit 1
}

Write-Output 'Installed Printers'
if ($printers.Count -eq 0) {
    Write-Output 'No installed printers were returned.'
    exit 0
}

What does this code do?

  • The first pipeline reports the current state of the local Spooler service.
  • The try block stores installed printer objects in $printers.
  • The catch block stops the report with a clear message when printer inventory cannot be retrieved.
  • The empty-inventory check exits cleanly when Windows returns no installed printers.
  • Save scripts\Get-PrinterHealth.ps1.

Before you run this partial report, which local printing details do you expect it to reveal?

  • Run the partial printer report from the existing Visual Studio Code terminal by running this command:
powershell.exe -NoProfile -File .\scripts\Get-PrinterHealth.ps1

What should you see?

You should see the local Print Spooler section followed by installed printer data. A workstation without installed printers reports that no printers were returned.

Printer inventory did not run?

Confirm that the file is inside the existing scripts folder. Check that the earlier PrintManagement availability test returned the required printer commands.

If clinic policy blocks the script, follow your organization's approved process. Do not add an execution-policy bypass.

Ask for help with this printer inventory failure.

Installed printer objects provide the input for queue inspection. Each printer can now be checked for current jobs.

  • Paste the queue-reporting block one blank line below the final brace in scripts\Get-PrinterHealth.ps1 using the code below:
$printers |
    Format-Table -AutoSize |
    Out-String -Width 200

$jobs = @(
    foreach ($printer in $printers) {
        Get-PrintJob -PrinterObject $printer -ErrorAction SilentlyContinue
    }
)

Write-Output 'Current Print Jobs'
if ($jobs.Count -eq 0) {
    Write-Output 'No queued print jobs were returned.'
}
else {
    $jobs |
        Format-Table -AutoSize |
        Out-String -Width 200
}

How does queue inspection work?

  • The first pipeline formats the installed printer collection for the report.
  • The foreach loop asks each printer for its current jobs.
  • The resulting jobs are stored in $jobs.
  • The final condition reports an empty queue clearly or formats the returned jobs.
  • Save scripts\Get-PrinterHealth.ps1.

Before you rerun the report, do you expect your local queue to contain any print jobs?

  • Run the completed printer health report by running this command:
powershell.exe -NoProfile -File .\scripts\Get-PrinterHealth.ps1

What does the completed report show?

You should see sections for the Print Spooler plus installed printers and current print jobs. An empty queue appears as a clear message instead of a missing section.

Printer jobs are missing?

An empty result is valid when no jobs are queued. If the script stops earlier, compare the placement of the $jobs block with the complete file below.

Ask for help checking the printer queue block.

✔️ Awesome, I've got everything!

Good work. Your printer report now reads service state plus printer inventory and queue data without changing the local printing configuration.

ⓧ I'd like to double check the full code

Compare your saved scripts\Get-PrinterHealth.ps1 file with this complete version.

Write-Output 'Print Spooler'
Get-Service -Name Spooler |
    Format-Table -AutoSize |
    Out-String -Width 200

try {
    $printers = @(Get-Printer -ErrorAction Stop)
}
catch {
    Write-Error "Printer inventory failed. $($_.Exception.Message)"
    exit 1
}

Write-Output 'Installed Printers'
if ($printers.Count -eq 0) {
    Write-Output 'No installed printers were returned.'
    exit 0
}

$printers |
    Format-Table -AutoSize |
    Out-String -Width 200

$jobs = @(
    foreach ($printer in $printers) {
        Get-PrintJob -PrinterObject $printer -ErrorAction SilentlyContinue
    }
)

Write-Output 'Current Print Jobs'
if ($jobs.Count -eq 0) {
    Write-Output 'No queued print jobs were returned.'
}
else {
    $jobs |
        Format-Table -AutoSize |
        Out-String -Width 200
}

How to compare this file

Check the order of the Spooler section plus printer inventory and queue inspection. Keep the blank lines between logical sections.

Report workstation health

Printer symptoms can originate from the workstation itself. CIM exposes local operating system and disk information through structured Windows data.

  • Use the new-file control in the Visual Studio Code file sidebar to create scripts\Get-WorkstationHealth.ps1.

You should see Get-WorkstationHealth.ps1 listed inside the existing scripts folder.

  • Paste the operating system summary block into scripts\Get-WorkstationHealth.ps1 using the code below:
try {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
    $disks = Get-CimInstance `
        -ClassName Win32_LogicalDisk `
        -Filter 'DriveType=3' `
        -ErrorAction Stop
}
catch {
    Write-Error "Workstation inventory failed. $($_.Exception.Message)"
    exit 1
}

$summary = [PSCustomObject]@{
    Computer       = $os.CSName
    OperatingSystem = $os.Caption
    Version        = $os.Version
    LastBootUpTime = $os.LastBootUpTime
}

Write-Output 'Workstation Summary'
$summary | Format-Table -AutoSize | Out-String -Width 200

What does this code do?

  • The first query stores local operating system data in $os.
  • The second query stores local fixed disks in $disks.
  • The $summary object selects the computer name plus operating system details and last boot time.
  • The error path stops the report when workstation inventory cannot be read.
  • Save scripts\Get-WorkstationHealth.ps1.

Before you run this first workstation report, which operating system details do you expect to recognize?

  • Run the operating system summary by running this command:
powershell.exe -NoProfile -File .\scripts\Get-WorkstationHealth.ps1

What should you see?

You should see a Workstation Summary table. It includes the computer name plus Windows caption, version, and last boot time.

Workstation summary did not appear?

Check that each backtick is the final character on its line. Extra spaces after a continuation character can break a PowerShell command.

Ask for help checking the workstation inventory block.

Disk capacity adds a local storage signal to the report. Values in bytes become easier-to-read gigabyte figures.

  • Paste the disk summary block one blank line below the existing output pipeline using the code below:
$diskSummary = foreach ($disk in $disks) {
    [PSCustomObject]@{
        Drive       = $disk.DeviceID
        Volume      = $disk.VolumeName
        SizeGB      = '{0:N1}' -f ($disk.Size / 1GB)
        FreeSpaceGB = '{0:N1}' -f ($disk.FreeSpace / 1GB)
    }
}

Write-Output 'Local Disk Capacity'
$diskSummary | Format-Table -AutoSize | Out-String -Width 200

How is disk capacity calculated?

  • The loop creates one summary object for each local fixed disk.
  • The SizeGB value converts total bytes into gigabytes.
  • The FreeSpaceGB value converts available bytes into gigabytes.
  • The formatting expression keeps each capacity value to one decimal place.
  • Save scripts\Get-WorkstationHealth.ps1.

Before you rerun the report, which local drive do you expect to see first?

  • Run the workstation report with disk capacity by running this command:
powershell.exe -NoProfile -File .\scripts\Get-WorkstationHealth.ps1

What changed in the report?

You should now see a Local Disk Capacity table after the workstation summary. Each fixed disk shows its drive identifier plus volume name, total size, and free space.

Disk capacity is missing?

Confirm that $diskSummary is placed after the workstation summary. Check that the loop reads from the existing $disks collection.

Ask for help checking the disk summary block.

Recent serious System events add a time-bounded diagnostic view. The query keeps the report focused on up to ten critical or error events from the last day.

  • Paste the event-reporting block one blank line below the disk output pipeline using the code below:
$events = @(
    Get-WinEvent `
        -FilterHashtable @{
            LogName   = 'System'
            Level     = 1, 2
            StartTime = (Get-Date).AddDays(-1)
        } `
        -MaxEvents 10 `
        -ErrorAction SilentlyContinue
)

Write-Output 'Recent Critical and Error Events'
if ($events.Count -eq 0) {
    Write-Output 'No matching System events were returned for the last day.'
}
else {
    $events |
        Select-Object -Property TimeCreated, Id, LevelDisplayName, ProviderName |
        Format-Table -AutoSize |
        Out-String -Width 200
}

How does event filtering work?

  • The filter reads from the Windows System log.
  • The level filter keeps Critical and Error events.
  • The start time limits the query to the previous day.
  • The maximum event count keeps the report to ten records.
  • The final table selects the time plus event identifier, severity, and provider.
  • Save scripts\Get-WorkstationHealth.ps1.

Before you run the completed report, do you expect your workstation to have matching System events from the last day?

  • Run the completed workstation health report by running this command:
powershell.exe -NoProfile -File .\scripts\Get-WorkstationHealth.ps1

What does the completed report show?

You should see the workstation summary plus local disk capacity and recent serious System events. A quiet event log produces a clear no-matches message.

Event results are incomplete?

No matching events is a valid outcome. If the section is absent, compare the filter hashtable plus its braces and line continuations with the complete file below.

Ask for help checking the event filter.

✔️ Awesome, I've got everything!

That is a useful diagnostic milestone. Your workstation report now connects operating system identity with boot time, storage capacity, and recent serious events.

ⓧ I'd like to double check the full code

Compare your saved scripts\Get-WorkstationHealth.ps1 file with this complete version.

try {
    $os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
    $disks = Get-CimInstance `
        -ClassName Win32_LogicalDisk `
        -Filter 'DriveType=3' `
        -ErrorAction Stop
}
catch {
    Write-Error "Workstation inventory failed. $($_.Exception.Message)"
    exit 1
}

$summary = [PSCustomObject]@{
    Computer       = $os.CSName
    OperatingSystem = $os.Caption
    Version        = $os.Version
    LastBootUpTime = $os.LastBootUpTime
}

Write-Output 'Workstation Summary'
$summary | Format-Table -AutoSize | Out-String -Width 200

$diskSummary = foreach ($disk in $disks) {
    [PSCustomObject]@{
        Drive       = $disk.DeviceID
        Volume      = $disk.VolumeName
        SizeGB      = '{0:N1}' -f ($disk.Size / 1GB)
        FreeSpaceGB = '{0:N1}' -f ($disk.FreeSpace / 1GB)
    }
}

Write-Output 'Local Disk Capacity'
$diskSummary | Format-Table -AutoSize | Out-String -Width 200

$events = @(
    Get-WinEvent `
        -FilterHashtable @{
            LogName   = 'System'
            Level     = 1, 2
            StartTime = (Get-Date).AddDays(-1)
        } `
        -MaxEvents 10 `
        -ErrorAction SilentlyContinue
)

Write-Output 'Recent Critical and Error Events'
if ($events.Count -eq 0) {
    Write-Output 'No matching System events were returned for the last day.'
}
else {
    $events |
        Select-Object -Property TimeCreated, Id, LevelDisplayName, ProviderName |
        Format-Table -AutoSize |
        Out-String -Width 200
}

How to compare this file

Check that the operating system query comes first. The disk summary follows it. The event filter closes the file.

Guard the Print Spooler repair

A service restart can interrupt active print jobs. This script isolates that state-changing action behind a $Preview switch.

The preview path uses -WhatIf so the local Spooler remains untouched. An actual restart relies on existing Windows permissions and contains no embedded credentials.

  • Use the new-file control in the Visual Studio Code file sidebar to create scripts\Restart-PrintSpooler.ps1.

You should see Restart-PrintSpooler.ps1 listed inside the existing scripts folder.

  • Paste the guarded restart logic into scripts\Restart-PrintSpooler.ps1 using the code below:
param(
    [switch]$Preview
)

if ($Preview) {
    Write-Output 'Preview only. The Print Spooler will not be restarted.'
    Restart-Service -Name Spooler -WhatIf
    exit 0
}

try {
    Restart-Service `
        -Name Spooler `
        -PassThru `
        -ErrorAction Stop |
        Format-Table -AutoSize |
        Out-String -Width 200
}
catch {
    Write-Error "The Print Spooler was not restarted. If access was denied, reopen the toolbar from an elevated PowerShell session. $($_.Exception.Message)"
    exit 1
}

How is the repair guarded?

  • The parameter block defines the optional $Preview switch.
  • Preview mode announces that no restart occurs.
  • The -WhatIf parameter shows the proposed service action.
  • The normal path targets only the local Spooler service.
  • The error path explains that an actual restart can require an elevated PowerShell session.
  • Save scripts\Restart-PrintSpooler.ps1.

Before you test the guard, do you think the Print Spooler service state will change?

  • Test only the preview path by running this command:
powershell.exe -NoProfile -File .\scripts\Restart-PrintSpooler.ps1 -Preview

What should the preview prove?

You should see the preview message plus WhatIf output. The local Print Spooler is not restarted.

Preview attempted a real restart?

Stop and compare the if ($Preview) block with the complete file below. Confirm that Restart-Service -Name Spooler -WhatIf appears inside that block.

Ask for help checking the preview guard.

✔️ Awesome, I've got everything!

Your repair boundary is now explicit. Diagnostics remain read-only while the only state-changing action stays separate and previewable.

ⓧ I'd like to double check the full code

Compare your saved scripts\Restart-PrintSpooler.ps1 file with this complete version.

param(
    [switch]$Preview
)

if ($Preview) {
    Write-Output 'Preview only. The Print Spooler will not be restarted.'
    Restart-Service -Name Spooler -WhatIf
    exit 0
}

try {
    Restart-Service `
        -Name Spooler `
        -PassThru `
        -ErrorAction Stop |
        Format-Table -AutoSize |
        Out-String -Width 200
}
catch {
    Write-Error "The Print Spooler was not restarted. If access was denied, reopen the toolbar from an elevated PowerShell session. $($_.Exception.Message)"
    exit 1
}

How to compare this file

Check that the preview branch exits before the normal restart path. Confirm that the normal path targets only the local Spooler service.

The three scripts now cover printer state plus workstation health and a guarded repair preview. One final run confirms that each path works from the project folder.

Before you run all three checks, which command do you expect to produce a proposed action instead of a diagnostic table?

  • Run the final printer, workstation, and preview checks by running these commands:
powershell.exe -NoProfile -File .\scripts\Get-PrinterHealth.ps1
powershell.exe -NoProfile -File .\scripts\Get-WorkstationHealth.ps1
powershell.exe -NoProfile -File .\scripts\Restart-PrintSpooler.ps1 -Preview

What does the final check prove?

  • The printer report shows the local Spooler plus installed printers and current jobs.
  • The workstation report shows operating system details plus boot time, local disk capacity, and up to ten recent serious System events.
  • The repair script shows WhatIf output without restarting the service.

You have completed the safety boundary for this project. The diagnostic scripts report local health while the repair remains isolated behind preview and existing Windows permissions.

Your printer and workstation checks are ready. Next, you will bring these scripts together in a responsive right-edge toolbar.

Build the Right-Edge Toolbar

Your diagnostic scripts now cover network visibility, clinic device reachability, printer health, workstation health, and a guarded repair. Each script still requires a separate terminal command.

A right-edge Tkinter toolbar turns those scripts into one workflow. Its event loop stays responsive while each diagnostic runs in a separate process.

In this step, get ready to:
  • Build a right-aligned toolbar with diagnostic buttons, status text, and a results area.
  • Launch each existing script without freezing the interface.
  • Save timestamped reports and protect the Print Spooler restart with confirmation.
Create the window and command map

The toolbar needs one configuration list that connects each button label to its existing script. Project-relative paths keep those connections working from anywhere on this Windows computer.

  • Use the file sidebar in Visual Studio Code to create toolbar.py inside the clinic-it-quickcheck folder.
  • Add the imports, project paths, command specifications, and runtime state by copying this code into toolbar.py:
import os
import subprocess
import tkinter as tk
from datetime import datetime
from pathlib import Path
from tkinter import messagebox, ttk

BASE_DIR = Path(__file__).resolve().parent
SCRIPTS_DIR = BASE_DIR / "scripts"
LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(exist_ok=True)

TOOL_SPECS = [
    ("quick-network", "Quick Network Snapshot", ["cmd.exe", "/d", "/c", str(SCRIPTS_DIR / "Quick-Network-Check.bat")], False),
    ("device-status", "Clinic Device Status", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-ClinicDeviceStatus.ps1")], False),
    ("printer-health", "Printer Health", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-PrinterHealth.ps1")], False),
    ("workstation-health", "Workstation Health", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-WorkstationHealth.ps1")], False),
    ("restart-spooler", "Restart Print Spooler", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Restart-PrintSpooler.ps1")], True),
]

active_process = None
tool_buttons = []

What does this code set up?

  • The imports provide the window toolkit, process launcher, timestamps, local paths, and dialog helpers.
  • The path constants locate the existing scripts folder and local logs folder from the location of toolbar.py.
  • The TOOL_SPECS list stores each report key, button label, command argument list, and confirmation requirement.
  • The final Boolean value is True only for the state-changing Print Spooler action.
  • Save toolbar.py.
  • Check that the foundation loads by running this command in the Visual Studio Code terminal:
python toolbar.py

What does this check prove?

Python loads every import and evaluates the command map. A clean return to the terminal prompt proves this foundation contains no startup exception.

Good start. The toolbar configuration now loads cleanly with every existing script referenced through the project folder.

Does the foundation fail to load?

Check that every quote and closing bracket in TOOL_SPECS matches the code above. Confirm that toolbar.py sits beside the scripts folder.

Ask for help with the startup error.

A floating utility needs enough room for controls and report output. Screen-width geometry places the window near the right edge without adding native Windows shell integration.

  • Place the window and interface code directly below tool_buttons = []:
root = tk.Tk()
root.title("Clinic IT QuickCheck")
window_width = 720
window_height = 620
screen_width = root.winfo_screenwidth()
right_edge_x = max(screen_width - window_width - 20, 0)
root.geometry("{}x{}+{}+{}".format(window_width, window_height, right_edge_x, 40))
root.minsize(620, 500)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)

button_panel = ttk.Frame(root, padding=12)
button_panel.grid(row=0, column=0, sticky="ns")
output_panel = ttk.Frame(root, padding=(0, 12, 12, 12))
output_panel.grid(row=0, column=1, sticky="nsew")
output_panel.grid_rowconfigure(1, weight=1)
output_panel.grid_columnconfigure(0, weight=1)

ttk.Label(button_panel, text="Clinic IT QuickCheck").pack(fill="x", pady=(0, 8))
ttk.Label(button_panel, text="Diagnostics only. No patient data.", wraplength=190).pack(fill="x", pady=(0, 12))
status_var = tk.StringVar(value="Ready")
ttk.Label(output_panel, textvariable=status_var).grid(row=0, column=0, sticky="ew", pady=(0, 8))
output_box = tk.Text(output_panel, wrap="word")
output_box.grid(row=1, column=0, sticky="nsew")
output_box.insert("1.0", "Choose a diagnostic from the toolbar.")

root.mainloop()

How does the interface fit together?

  • The geometry calculation subtracts the window width and a 20-pixel margin from the screen width.
  • The left frame holds the vertical tool controls.
  • The right frame expands to hold status text and completed report output.
  • The safety label keeps the diagnostic scope visible during clinic triage.
  • The final mainloop() starts the interface event loop.
  • Save toolbar.py.
  • Launch the first visible version of the toolbar by running:
python toolbar.py

What should this launch show?

The command starts the Tkinter event loop. The terminal remains occupied until you close the toolbar window.

You should see a window near the right edge of the screen. It shows the clinic safety note, a Ready status, and an empty results area prompting you to choose a diagnostic.

Is the window missing or misplaced?

Confirm that the earlier Tkinter check opened its demonstration window. Check the terminal for a line number if toolbar.py contains a typing error.

Ask for help with the window geometry.

Keep the toolbar responsive

A diagnostic can take longer than one interface update. The toolbar launches each script with subprocess.Popen so the window can continue processing clicks and redraws.

Why poll the process?

A blocking process call would hold the Tkinter event loop until the diagnostic finishes. Short timer checks let the event loop stay active while the child process runs.

Each completed process leaves a return code and a timestamped local report. That creates an audit trail without sending diagnostic data elsewhere.

  • Close the running toolbar window.
  • Insert the output helper directly above root.mainloop():
def show_output(text):
    output_box.delete("1.0", tk.END)
    output_box.insert(tk.END, text)

What does the output helper do?

The show_output() helper clears the previous result before inserting new text. The results area always reflects the latest toolbar action.

  • Save toolbar.py.
  • Confirm the helper loads by running:
python toolbar.py

What does this run verify?

The toolbar should open with the same status and prompt. This proves the new helper is defined before the event loop starts.

Does the results area fail to appear?

Check that show_output() is aligned at the left edge. Keep both statements inside the function indented by four spaces.

Ask for help with the helper indentation.

  • Close the toolbar window.
  • Insert the button-state helper below show_output():
def set_tool_buttons_enabled(enabled):
    state = "normal" if enabled else "disabled"
    for button in tool_buttons:
        button.configure(state=state)

Why manage every button together?

The helper applies one state to every diagnostic button stored in tool_buttons. Disabling the group prevents overlapping diagnostics from competing for the shared output area.

  • Save toolbar.py.
  • Confirm the second helper loads by running:
python toolbar.py

What does this run verify?

The toolbar should open normally with its safety note and results area. Python has now loaded both interface helpers without an exception.

Seeing a name or indentation error?

Check that the loop uses the existing tool_buttons list. Keep button.configure(state=state) indented inside the loop.

Ask for help with the button-state helper.

The polling helper checks whether the child process has finished. It schedules another check after 250 milliseconds while the process remains active.

  • Close the toolbar window.
  • Insert the process polling function below set_tool_buttons_enabled():
def poll_process(process, log_file, log_path, label):
    global active_process
    return_code = process.poll()
    if return_code is None:
        root.after(250, poll_process, process, log_file, log_path, label)
        return
    log_file.close()
    active_process = None
    set_tool_buttons_enabled(True)
    try:
        report_text = log_path.read_text(encoding="utf-8", errors="replace")
    except OSError as error:
        report_text = "Could not read the report: {}".format(error)
    status_var.set("{} finished with exit code {}".format(label, return_code))
    show_output("Report: {}\n\n{}".format(log_path.name, report_text))

How does polling finish a report?

  • The process returns None while it is still running.
  • The timer schedules another check without blocking the interface.
  • A completed process closes its report file and restores the buttons.
  • The report reader replaces undecodable characters so one unusual command character cannot hide the rest of the output.
  • The status line records the diagnostic label and return code.
  • Save toolbar.py.
  • Confirm the polling function loads by running:
python toolbar.py

What does this run verify?

The window should remain open with its normal layout. This clean launch confirms Python can define the timer callback and report-reading path.

Does the polling function stop startup?

Check that the try block aligns with the earlier completed-process statements. Keep its except block at the same indentation level.

Ask for help with the polling traceback.

The launcher creates one timestamped report before starting a process. It also keeps the state-changing repair behind an explicit confirmation dialog.

  • Close the toolbar window.
  • Insert the command launcher below poll_process():
def launch_tool(key, label, command, needs_confirmation):
    global active_process
    if active_process is not None:
        messagebox.showwarning("Tool already running", "Wait for the current diagnostic to finish.")
        return
    if needs_confirmation and not messagebox.askyesno("Confirm Print Spooler restart", "This can interrupt active print jobs. Check the queue first. Continue?"):
        return
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    log_path = LOG_DIR / "{}-{}.txt".format(key, timestamp)
    log_file = log_path.open("wb")
    try:
        active_process = subprocess.Popen(command, cwd=str(BASE_DIR), stdout=log_file, stderr=subprocess.STDOUT, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
    except OSError as error:
        log_file.close()
        active_process = None
        messagebox.showerror("Launch failed", "{} could not start: {}".format(label, error))
        return
    set_tool_buttons_enabled(False)
    status_var.set("Running {}...".format(label))
    show_output("The diagnostic is running. The toolbar remains responsive.")
    root.after(250, poll_process, active_process, log_file, log_path, label)

How does the launcher protect the workflow?

  • The active-process check prevents a second command from starting during an existing diagnostic.
  • The confirmation condition applies only when the command specification marks an action as state-changing.
  • The timestamp creates a unique report name for each completed launch.
  • The argument list goes directly to Popen without embedding a password or constructing a shell command string.
  • The timer hands process monitoring back to poll_process().
  • Save toolbar.py.
  • Confirm the launcher definition loads by running:
python toolbar.py

What does this run verify?

The toolbar should open without a traceback. Its controls arrive in the next chunk, so this run confirms the complete process-launch path can load safely.

Does the launcher raise an error?

Check the long subprocess.Popen line for matching parentheses. Confirm that poll_process() appears above launch_tool().

Ask for help with the launcher traceback.

Reports stay inside the existing logs folder. The reports action uses the Windows file association to open that folder for review.

  • Close the toolbar window.
  • Insert the reports-folder helper below launch_tool():
def open_reports():
    try:
        os.startfile(str(LOG_DIR))
    except OSError as error:
        messagebox.showerror("Open Reports failed", "The reports folder could not be opened: {}".format(error))

What does the reports helper do?

The helper asks Windows to open the local logs folder with its associated file browser. A dialog presents any operating-system error instead of ending the toolbar.

  • Save toolbar.py.
  • Confirm every helper loads by running:
python toolbar.py

What does this run verify?

The right-edge window should open with the same layout. The complete helper layer now loads before the event loop starts.

Does the reports helper stop startup?

Confirm that open_reports() is above root.mainloop(). Check that the error dialog remains indented inside the except block.

Ask for help with the reports helper.

Add the controls and verify the safeguards

Each configured diagnostic now needs a button that passes its stored values into launch_tool(). Default arguments in the button callback preserve the values from each loop iteration.

  • Close the toolbar window.
  • Insert the button-building code directly above root.mainloop():
for key, label, command, needs_confirmation in TOOL_SPECS:
    button = ttk.Button(button_panel, text=label, command=lambda k=key, l=label, c=command, n=needs_confirmation: launch_tool(k, l, c, n))
    button.pack(fill="x", pady=4)
    tool_buttons.append(button)
ttk.Button(button_panel, text="Open Reports", command=open_reports).pack(fill="x", pady=(16, 4))
ttk.Button(button_panel, text="Exit", command=root.destroy).pack(fill="x", pady=4)

How do the controls connect?

  • The loop creates one button from every entry in TOOL_SPECS.
  • Each diagnostic button joins tool_buttons so the launcher can disable the group during a command.
  • The reports control opens the local report folder.
  • The exit control closes the Tkinter window.

Before you run the finished toolbar, which parts of the interface do you expect to remain usable while a diagnostic is still running?

  • Save toolbar.py.
  • Launch the completed toolbar by running:
python toolbar.py

What should happen now?

The window opens near the right edge with five tool buttons. It also includes separate controls for reports and exit.

A running diagnostic updates the status area and disables the diagnostic buttons. The window continues repainting because the timer polls the child process.

  • Click Printer Health in the toolbar.

You should see a running status while the diagnostic is active. The diagnostic buttons become unavailable until the process finishes.

When the check completes, you should see the report filename, Print Spooler details, installed printers, and current print-job output. The status shows the process exit code.

  • Click Open Reports.

You should see the local logs folder with a timestamped printer-health text report. That file confirms the toolbar preserved the script output locally.

Restarting the Print Spooler can interrupt active jobs. The next check confirms the guard without approving the restart.

  • Return to the toolbar.
  • Click Restart Print Spooler.

You should see a confirmation dialog explaining the effect on active print jobs. The repair does not start until you approve that dialog.

  • Decline the confirmation to leave the Print Spooler unchanged.
  • Review the string values in toolbar.py.
  • Confirm that the file contains no passwords.
  • Confirm that the file contains no patient data.

✔️ Awesome, I've got everything!

Excellent work. Your right-edge toolbar now launches the existing diagnostics, remains responsive, protects the repair action, and stores timestamped reports locally.

ⓧ I'd like to double check the full code

Compare your complete toolbar.py with this reference. Keep every function above the final root.mainloop() line.

import os
import subprocess
import tkinter as tk
from datetime import datetime
from pathlib import Path
from tkinter import messagebox, ttk

BASE_DIR = Path(__file__).resolve().parent
SCRIPTS_DIR = BASE_DIR / "scripts"
LOG_DIR = BASE_DIR / "logs"
LOG_DIR.mkdir(exist_ok=True)

TOOL_SPECS = [
    ("quick-network", "Quick Network Snapshot", ["cmd.exe", "/d", "/c", str(SCRIPTS_DIR / "Quick-Network-Check.bat")], False),
    ("device-status", "Clinic Device Status", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-ClinicDeviceStatus.ps1")], False),
    ("printer-health", "Printer Health", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-PrinterHealth.ps1")], False),
    ("workstation-health", "Workstation Health", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Get-WorkstationHealth.ps1")], False),
    ("restart-spooler", "Restart Print Spooler", ["powershell.exe", "-NoProfile", "-File", str(SCRIPTS_DIR / "Restart-PrintSpooler.ps1")], True),
]

active_process = None
tool_buttons = []

root = tk.Tk()
root.title("Clinic IT QuickCheck")
window_width = 720
window_height = 620
screen_width = root.winfo_screenwidth()
right_edge_x = max(screen_width - window_width - 20, 0)
root.geometry("{}x{}+{}+{}".format(window_width, window_height, right_edge_x, 40))
root.minsize(620, 500)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=1)

button_panel = ttk.Frame(root, padding=12)
button_panel.grid(row=0, column=0, sticky="ns")
output_panel = ttk.Frame(root, padding=(0, 12, 12, 12))
output_panel.grid(row=0, column=1, sticky="nsew")
output_panel.grid_rowconfigure(1, weight=1)
output_panel.grid_columnconfigure(0, weight=1)

ttk.Label(button_panel, text="Clinic IT QuickCheck").pack(fill="x", pady=(0, 8))
ttk.Label(button_panel, text="Diagnostics only. No patient data.", wraplength=190).pack(fill="x", pady=(0, 12))
status_var = tk.StringVar(value="Ready")
ttk.Label(output_panel, textvariable=status_var).grid(row=0, column=0, sticky="ew", pady=(0, 8))
output_box = tk.Text(output_panel, wrap="word")
output_box.grid(row=1, column=0, sticky="nsew")
output_box.insert("1.0", "Choose a diagnostic from the toolbar.")

def show_output(text):
    output_box.delete("1.0", tk.END)
    output_box.insert(tk.END, text)

def set_tool_buttons_enabled(enabled):
    state = "normal" if enabled else "disabled"
    for button in tool_buttons:
        button.configure(state=state)

def poll_process(process, log_file, log_path, label):
    global active_process
    return_code = process.poll()
    if return_code is None:
        root.after(250, poll_process, process, log_file, log_path, label)
        return
    log_file.close()
    active_process = None
    set_tool_buttons_enabled(True)
    try:
        report_text = log_path.read_text(encoding="utf-8", errors="replace")
    except OSError as error:
        report_text = "Could not read the report: {}".format(error)
    status_var.set("{} finished with exit code {}".format(label, return_code))
    show_output("Report: {}\n\n{}".format(log_path.name, report_text))

def launch_tool(key, label, command, needs_confirmation):
    global active_process
    if active_process is not None:
        messagebox.showwarning("Tool already running", "Wait for the current diagnostic to finish.")
        return
    if needs_confirmation and not messagebox.askyesno("Confirm Print Spooler restart", "This can interrupt active print jobs. Check the queue first. Continue?"):
        return
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    log_path = LOG_DIR / "{}-{}.txt".format(key, timestamp)
    log_file = log_path.open("wb")
    try:
        active_process = subprocess.Popen(command, cwd=str(BASE_DIR), stdout=log_file, stderr=subprocess.STDOUT, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
    except OSError as error:
        log_file.close()
        active_process = None
        messagebox.showerror("Launch failed", "{} could not start: {}".format(label, error))
        return
    set_tool_buttons_enabled(False)
    status_var.set("Running {}...".format(label))
    show_output("The diagnostic is running. The toolbar remains responsive.")
    root.after(250, poll_process, active_process, log_file, log_path, label)

def open_reports():
    try:
        os.startfile(str(LOG_DIR))
    except OSError as error:
        messagebox.showerror("Open Reports failed", "The reports folder could not be opened: {}".format(error))

for key, label, command, needs_confirmation in TOOL_SPECS:
    button = ttk.Button(button_panel, text=label, command=lambda k=key, l=label, c=command, n=needs_confirmation: launch_tool(k, l, c, n))
    button.pack(fill="x", pady=4)
    tool_buttons.append(button)
ttk.Button(button_panel, text="Open Reports", command=open_reports).pack(fill="x", pady=(16, 4))
ttk.Button(button_panel, text="Exit", command=root.destroy).pack(fill="x", pady=4)
root.mainloop()

How should you use this reference?

This block shows the exact cumulative file for comparison. The command map, interface, helpers, controls, and final event loop should appear in this order.

Does a diagnostic fail from the toolbar?

Check the displayed exit code and report text first. Confirm that every script filename in TOOL_SPECS matches the existing file inside scripts.

If the interface freezes, confirm that launch_tool() uses subprocess.Popen and schedules poll_process() with root.after.

Ask for help with the failed toolbar diagnostic.

Secret mission

Add an All Safe Checks Button

Four separate diagnostic buttons still take time to run during clinic triage. Add an All Safe Checks button that queues the four read-only diagnostics, saves each report, and presents one completion summary without including the Print Spooler repair.

Clean Up Your Resources

Clean Up Your Resources

Everything you built stays on your Windows computer, so this project creates no cloud services or ongoing costs. Decide whether to keep the local utility available, close it for later, or delete it entirely.

Resources you used:

  • The running Clinic IT QuickCheck toolbar process, if the window is still open.
  • The clinic-it-quickcheck folder contains the complete local utility. Its contents include toolbar.py, config\targets.csv, scripts, and logs.

Keep everything running

No action is needed. Choose this option while you are actively using the toolbar for authorized clinic triage.

  • Keep the clinic-it-quickcheck folder on an access-controlled clinic workstation.
  • Review the authorized addresses in config\targets.csv under your clinic's device-management process.
  • Review generated reports in logs under your clinic's retention policy.
  • Remove reports that your clinic no longer requires.

Pause - I'll come back to this later

Close the toolbar to end the current session. Your files remain ready for the next approved triage session.

  • Wait for the current diagnostic to finish if the status shows a running check.
  • Click Exit in the toolbar's button panel.
  • Leave the clinic-it-quickcheck folder in its current location.

The toolbar window closes. The project leaves nothing running in the background.

Delete - I don't want to use this again

Deleting the folder is permanent, so take a moment to protect any reports your clinic must retain. This cleanup removes the application, configuration, scripts, and generated reports together.

  • Wait for the current diagnostic to finish if the status shows a running check.
  • Click Exit in the toolbar's button panel.
  • Review each report in logs for patient information.
  • Retain only the reports required by clinic policy.
  • Transfer retained reports only to an approved clinic storage location.

Once required reports are protected, the clinic-it-quickcheck folder is the only project resource left to remove.

  • Close the current Visual Studio Code window.
  • Navigate to the location where you created clinic-it-quickcheck using the Windows file manager.
  • Right-click the clinic-it-quickcheck folder.
  • Select Delete.
  • Remove only clinic-it-quickcheck from the Recycle Bin.

You should no longer see clinic-it-quickcheck at its original location. The toolbar process is closed.

Nice Work!

Nice Work!

That is a strong finish! You built a right-edge clinic IT toolbar that runs authorized diagnostics without freezing the interface. Each completed check now leaves a timestamped local report.

You've learned how to:

  • Build a read-only Batch network snapshot that shows the workstation hostname. It also reveals the complete TCP/IP configuration plus the current ARP cache.
  • Use PowerShell to run configuration-driven ICMP reachability checks for authorized clinic device roles. You also built printer queue plus workstation health reports. A guarded preview protects the state-changing Print Spooler restart.
  • Create a responsive Tkinter toolbar that launches each diagnostic as a background process. Timer-based polling keeps the interface responsive. Timestamped reports preserve the output inside the local logs folder.
  • Secret Mission: Added an All Safe Checks queue that runs four read-only diagnostics in sequence. It records every return code. It creates four reports. It always excludes Restart Print Spooler.

Ready to quiz yourself?