W tym artykule zbudujemy praktyczny mini-projekt, który łączy kluczowe elementy PowerShella w jedno spójne rozwiązanie. Wykorzystasz m.in. pracę z plikami, funkcje, pętle, instrukcje warunkowe, moduły oraz import i eksport danych, aby stworzyć skrypt generujący cykliczny raport systemowy. Celem ćwiczenia jest pokazanie, jak projektować proste, użyteczne automatyzacje, które można dalej rozwijać i dostosowywać do realnych potrzeb administracyjnych.
- Pomysł na projekt raportu systemowego
- Dlaczego właśnie ten projekt?
- Krok 0: Architektura
- Krok 1: Konfiguracja i parametry
- Krok 2: Funkcje pomocnicze – Zbieranie danych
- Initialize-PSWriteHTML – sprawdza i importuje wymagany moduł
- Get-ComputerData – Podstawowe informacje o systemie
- Get-ProcessorData – Charakterystyka procesora
- Get-MemoryData – Analiza pamięci RAM
- Get-BIOSData – Informacje o firmware
- Get-DiskData – Monitoring przestrzeni dyskowej
- Get-NetworkData – Konfiguracja sieciowa
- Get-LocalUsersData – Audyt kont użytkowników
- Krok 3: Główna logika – Generowanie raportu
- Cały skrypt
- Użytkowanie
- Podsumowanie
Pomysł na projekt raportu systemowego
Każdy projekt, a tym bardziej ten dotyczący automatyzacji powinien zaczynać się od jasno zdefiniowanej potrzeby biznesowej lub operacyjnej. I chociaż nasz projekt będzie typowo edukacyjny, to wcale nie oznacza, że nie może rozwiązywać realnych problemów. Obecnie jednym z takich zmartwień w wielu środowiskach IT jest ręczne zbieranie informacji o stanie systemów, konfiguracji oraz sprzętu. Czynności takie jak audyty, monitoring parametrów czy dokumentowanie konfiguracji są niezbędne, jednak w dłuższej perspektywie bardzo czasochłonne. W odpowiedzi na ten problem zaprojektujemy prosty skrypt, którego zadaniem będzie zebranie kilku podstawowych informacji i przedstawienie ich w eleganckiej, czytelnej formie. Na potrzeby projektu załóżmy, że raport będzie obejmował następujące dane
- Urządzenie (model, nazwa, system operacyjny, architektura itd.)
- Procesor
- BIOS
- Dyski i ich wykorzystanie
- Pamięć RAM
- Ustawienia sieci
- Lokalni użytkownicy
Tak wiem. Na pierwszy rzut oka wydaje się sporo, ale jak sam zobaczysz nie jest to wcale tak skomplikowane jak wygląda. Zanim jednak przejdziemy do pisania poleceń, w pierwszej kolejności należy zastanowić się co tak naprawdę chcemy osiągnąć i w jaki sposób zbierzemy informacje oraz jak je potem wyświetlimy. Zakładam zatem, że w końcowym efektem naszego projektu będzie skrypt, który:
- zbierze informacje o systemie operacyjnym (możliwie prosto i optymalnie)
- sformatuje dane w czytelnej formie
- wygeneruje raport HTML, który można łatwo zapisać, wysłać lub zarchiwizować
Tak więc, na potrzeby naszego projektu utworzymy kilka funkcji pomocniczych zbierających dane, które automatycznie zapiszą je w odpowiedniej postaci. Dodatkowo użyjemy popularnego modułu PSWriteHTML (autorem tego cuda jest Pan Przemysław Kłys z firmy Evotec), który znacząco upraszcza generowanie estetycznych raportów HTML bez konieczności ręcznego pisania kodu.
Dlaczego właśnie ten projekt?
Jeżeli zastanawiasz się, dlaczego spośród setek możliwych projektów wybrałem właśnie ten. To sprawa jest dość prosta. Lubię szybkie, niewielkie projekty, które jednocześnie dają maksymalne efekty i sporo wnoszą w uproszczenie realnej pracy. Dodatkowo ten miniprojekt jest dobrym wyborem jako starter, ponieważ komponuje większość omówionych w ramach cyklu „PowerShell Podstawy” umiejętności. Tak więc, na podstawie tych kilkudziesięciu linijek kodu:
- zobaczysz, jak pracować na realnych obiektach i ich właściwościach
- poznasz możliwości zbierania danych systemowych
- nauczysz się używać pętli i warunków do przetwarzania danych
- zobaczysz jak można wyeksportować dane do czytelnego i „biznesowego” formatu (HTML)
- poznasz możliwości zewnętrznego modułu (PSWriteHTML)
- poznasz techniki wykorzystania harmonogramu zadań
- zbudujesz coś, co faktycznie może się przydać w pracy administratora
Jak zatem widzisz, nie ma co czekać, bierzemy się do dzieła.
Poniżej lista kroków, dzięki którymi zepniemy wszystkie elementy w całość.
Krok 0: Architektura
Zanim przejdziemy do dowolnego środowiska programistycznego i napiszemy pierwszą linijkę kodu. Warto by posiadać jakiś ogólny pogląd na to jak ma wyglądać nasz skrypt. Poniżej przygotowałem taką ogólną strukturę, dzięki czemu łatwiej mi planować kolejne sekcje. To działa trochę jak mapa podczas naszej skryptowej wędrówki.
1. KONFIGURACJA ├── Parametry wejściowe └── Zmienne konfiguracyjne 2. FUNKCJE POMOCNICZE ├── Get-ComputerData (dane komputera) ├── Get-ProcessorData (informacje o CPU) ├── Get-MemoryData (dane pamięci RAM) ├── Get-BIOSData (informacje BIOS) ├── Get-DiskData (dane dysków) ├── Get-NetworkData (konfiguracja sieci) └── Get-LocalUsersData (lokalni użytkownicy) 3. GŁÓWNA LOGIKA ├── Inicjalizacja modułu ├── Zbieranie danych └── Generowanie raportu HTML
Jak widzisz, podzieliłem całość na trzy główne komponenty:
Konfiguracja – tutaj znajdą się wszystkie elementy niezbędne do konfiguracji naszego skryptu, przykładowo parametry wejściowe, ścieżka do wygenerowanego raportu czy ustawienia kolorów.
Funkcje pomocnicze – w tej części zgromadziłem wszystkie funkcje, które wykorzystamy do zgromadzenia niezbędnych informacji.
Główna logika – komponent ten stanowi serce naszego projektu, to tutaj zachodzą najważniejsze procesy jak inicjacja modułu, zbieranie informacji czy generacja raportu.
Wymagania
- PowerShell 5.1 lub nowszy
(skrypt zadziała również w PowerShell 7+, natomiast dostępność niektórych modułów może się różnić). - Uprawnienia do odczytu informacji systemowych.
- Moduł PSWriteHTML.
Krok 1: Konfiguracja i parametry
Parametry wejściowe
[CmdletBinding()]
param(
[Parameter()]
[string]$OutputPath = "$Home\SystemReport.html"
)
#Requires -Version 5.1
Co się tutaj dzieje?
[CmdletBinding()]– nadaje skryptowi zaawansowane funkcjonalności cmdlet-ów, takie jak verbose output i error handlingparam()– definiuje parametr$OutputPathz wartością domyślną. Dzięki niemu możesz elastycznie określić lokalizację zapisu raportu. Jeżeli tego nie zrobisz raport domyślnie i tak się zapisze w lokalizacjiC:\Raports\Raport_Systemowy.html#Requires -Version 5.1– zapewnia, że skrypt uruchomi się tylko na PowerShell 5.1 lub nowszym
Zmienne konfiguracyjne
$Config = @{
HeaderColor = '#00364b'
}
Co się tutaj dzieje?
- Aktualnie niewiele tutaj jest, ale z założenia jest to miejsca dla danych konfiguracyjnych. Zmienna
$Configw tym wypadku hashtable z parametrami, obecnie przechowuje jedynie kolor nagłówków (#00364b– ciemny odcień niebieskozielonego). Dzięki centralizacji konfiguracji dużo łatwiej jest później zmieniać ustawienia.
Krok 2: Funkcje pomocnicze – Zbieranie danych
Initialize-PSWriteHTML – sprawdza i importuje wymagany moduł
Podczas korzystania z zewnętrznych bibliotek dobrą praktyką jest automatyzacja przygotowania środowiska pracy. Poniższa funkcja gwarantuje, że moduł PSWriteHTML jest obecny w systemie przed rozpoczęciem generowania raportu.
function Initialize-PSWriteHTML {
if (-not (Get-Module -ListAvailable -Name PSWriteHTML)) {
Write-Warning "Moduł PSWriteHTML nie jest zainstalowany."
Write-Host "Instalowanie modułu PSWriteHTML..." -ForegroundColor Yellow
try {
Install-Module -Name PSWriteHTML -Scope CurrentUser -Force -ErrorAction Stop
Write-Host "Moduł PSWriteHTML został pomyślnie zainstalowany." -ForegroundColor Green
}
catch {
Write-Error "Nie udało się zainstalować modułu PSWriteHTML: $_"
exit 1
}
}
Import-Module PSWriteHTML -ErrorAction Stop
}
Co tu się dzieje?
- Funkcja sprawdza, czy moduł
PSWriteHTMLjest zainstalowany w systemie za pomocą cmdletuGet-Module -ListAvailable. - W przypadku braku modułu, wyświetlany jest żółty komunikat informujący o rozpoczęciu instalacji.
- Następnie rozpoczyna się próba pobrania i instalacji modułu
Install-Moduledla bieżącego użytkownika-Scope CurrentUser(nie wymaga uprawnień administratora). - Po pomyślnej weryfikacji lub instalacji, moduł zostaje załadowany do bieżącej sesji PowerShella.
- Jeśli natomiast instalacja się nie powiedzie, skrypt wyświetla szczegółowy błąd i natychmiast przerywa działanie.
Get-ComputerData – Podstawowe informacje o systemie
Poniżej moja propozycja prostej funkcji zbierającej dane dotyczące systemu operacyjnego i urządzenia:
function Get-ComputerData {
try {
$ComputerInfo = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
$OSInfo = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
[ordered]@{
'Typ urządzenia' = $ComputerInfo.ChassisSKUNumber
'Nazwa komputera' = $ComputerInfo.Name
'Producent' = $ComputerInfo.Manufacturer
'Model' = $ComputerInfo.Model
'Domena/Grupa robocza' = if ($ComputerInfo.PartOfDomain) { $ComputerInfo.Domain } else { $ComputerInfo.Workgroup }
'System operacyjny' = $OSInfo.Caption
'Wersja OS' = $OSInfo.Version
'Architektura' = $OSInfo.OSArchitecture
'Data instalacji' = $OSInfo.InstallDate
'Ostatni rozruch' = $OSInfo.LastBootUpTime
'Bieżący użytkownik' = $ComputerInfo.UserName
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o komputerze: $_"
return @{}
}
}
Co tu się dzieje?
- Do zbierania informacji wykorzystuję cmdlet
Get-CimInstancei odpowiednie klasy:Win32_ComputerSystem– informacje o sprzęcie i konfiguracji komputeraWin32_OperatingSystem– dane systemu operacyjnego
- Element
[ordered]@{...}zapewnia, że dane w raporcie pojawią się w określonej kolejności i formie, a nie alfabetycznej, co znacznie upraszcza odczyt i pozwala na konkretną formę wyświetlenia. - Do wykrywania błędów zastosowałem konstrukcję
try - catch, która w tego typu zastosowaniach sprawdza się doskonale. Opcja-ErrorAction Stopsprawia, że każdy problem z dostępem do danych natychmiast przerywa bloktryi przechodzi do sekcjicatch. - Dodatkowo w przypadku domeny/grupy roboczej zastosowałem małą logikę obiektową
if {...}, dzięki czemu w zależności od konkretnego przypadku wyświetlana jest nazwa domeny (Jeśli komputer jest jej częścią) lub grupy roboczej (w przypadku komputera standalone). - Polecenie
Write-Errorwyświetla w konsoli czerwoną informację o napotkanym problemie wraz z jego technicznym opisem. - W razie awarii funkcja zwraca pusty zbiór danych
return @(), co zapobiega błędom w dalszych częściach skryptu.
Poniżej umieszczam przykładowe wywołanie funkcji dla mojej maszyny:
PS C:\Users\Admin> Get-ComputerData Name Value ---- ----- Typ urządzenia Notebook Nazwa komputera DELL Producent Dell Inc. Model Latitude 7490 Domena/Grupa robocza WORKGROUP System operacyjny Microsoft Windows 11 Pro Wersja OS 10.0.26200 Architektura 64-bit Data instalacji 12/01/2025 17:02:15 Ostatni rozruch 19/01/2026 16:13:19 Bieżący użytkownik Dell\Admin
Jak widzisz, funkcja działa całkiem nieźle i wyświetla wiele cennych danych.
Get-ProcessorData – Charakterystyka procesora
function Get-ProcessorData {
try {
$ProcessorInfo = Get-CimInstance -ClassName Win32_Processor -ErrorAction Stop
[ordered]@{
'Procesor' = $ProcessorInfo.Name
'Rdzenie' = $ProcessorInfo.NumberOfCores
'Procesory logiczne' = $ProcessorInfo.NumberOfLogicalProcessors
'Maksymalna częstotliwość' = "$($ProcessorInfo.MaxClockSpeed) MHz"
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o procesorze: $_"
return @{}
}
}
Co tu się dzieje?
- Struktura funkcji bliźniaczo podobna do poprzedniej. Również korzystamy z dobrodziejstwa cmdletu
Get-CimInstance, tym razem jednak odpytujemy klasęWin32_Processor. Dzięki temu dostajemy dostęp do informacji takich jak nazwa czy liczba rdzeni procesora.
Get-MemoryData – Analiza pamięci RAM
function Get-MemoryData {
try {
$ComputerInfo = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
$OSInfo = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
$TotalRAM = [math]::Round($ComputerInfo.TotalPhysicalMemory / 1GB, 2)
$FreeRAM = [math]::Round($OSInfo.FreePhysicalMemory / 1MB, 2)
$UsedRAM = [math]::Round($TotalRAM - $FreeRAM, 2)
[PSCustomObject]@{
'Całkowita RAM (GB)' = $TotalRAM
'Wolna RAM (GB)' = $FreeRAM
'Używana RAM (GB)' = $UsedRAM
'Wykorzystanie (%)' = [math]::Round(($UsedRAM / $TotalRAM) * 100, 2)
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o pamięci: $_"
return $null
}
}
Co tu się dzieje?
- Tym razem dobieramy się do informacji o pamięci RAM. Zauważ, że ponownie korzystamy z klas
Win32_ComputerSystemorazWin32_OperatingSystem, które już odpytywaliśmy. W związku z tym można by połączyć to w jedno zapytanie odnośnie sprzętu i RAM. Niemniej jednak w celach eksperymentalnych zostawmy tak, jak to jest obecnie (chyba, że masz ochotę to zmienić). - Dodatkowo zauważ, że dokonuję małej konwersji jednostek,
TotalPhysicalMemorypodawana jest w bajtach, w związku z tym warto zamienić to na GB. - Zwróć uwagę na konstruktor
[PSCustomObject]w miejsceHashTable. Tym razem potrzebna nam będzie tabela z danymi, a do tego lepiej sprawdzi się właśnie obiekt. Tworzymy zatem nową, czytelną strukturę danych z wybranymi właściwościami pamięci. - Wykorzystanie RAM w procentach, obliczamy stosując prosty wzór matematyczny:
($UsedRAM / $TotalRAM) * 100, natomiast zaokrąglenie do 2 liczb po przecinku osiągam za pomocą[math]::Round((),2).
Get-BIOSData – Informacje o firmware
function Get-BIOSData {
try {
$BIOSInfo = Get-CimInstance -ClassName Win32_BIOS -ErrorAction Stop
[ordered]@{
'Producent' = $BIOSInfo.Manufacturer
'Wersja' = $BIOSInfo.SMBIOSBIOSVersion
'Data wydania' = $BIOSInfo.ReleaseDate
'Numer seryjny' = $BIOSInfo.SerialNumber
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o BIOS: $_"
return @{}
}
}
Co tu się dzieje?
- W zasadzie nic nowego. Ponownie cmdlet
Get-CimInstancei odpowiednia klasa, która umożliwia pobranie danych dotyczących BIOSu.
Get-DiskData – Monitoring przestrzeni dyskowej
function Get-DiskData {
try {
Get-CimInstance -ClassName Win32_LogicalDisk -Filter "DriveType=3" -ErrorAction Stop |
ForEach-Object {
[PSCustomObject]@{
'Dysk' = $_.DeviceID
'System plików' = $_.FileSystem
'Pojemność (GB)' = [math]::Round($_.Size / 1GB, 2)
'Wolne miejsce (GB)' = [math]::Round($_.FreeSpace / 1GB, 2)
'Wykorzystane (%)' = [math]::Round((($_.Size - $_.FreeSpace) / $_.Size) * 100, 2)
}
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o dyskach: $_"
return @()
}
}
Co tu się dzieje?
- Tak jak poprzednio polecenie
Get-CimInstanceodpytuje klasę systemowąWin32_LogicalDiskw poszukiwaniu dostępnych dysków. - Dzięki parametrowi
-Filter "DriveType=3"wyniki ograniczone są wyłącznie do fizycznych dysków twardych, pomijając inne napędy. - Może się zdarzyć, że masz więcej niż jeden fizyczny dysk, wtedy operator potoku
|przekazuje listę znalezionych dysków do pętliForEach-Objectw celu ich indywidualnej obróbki. - Na koniec przypisanie odpowiednich wartości i konwersja jednostek.
💡Warto wiedzieć
Właściwość
DriveTypemoże przyjmować różne wartości. Poniżej znajduje się tabela wyjaśniająca co i jak:
Wartość Typ napędu Opis 0 Unknown Nieznany typ napędu 1 No root Directory Brak katalogu głównego (nieprawidłowy napęd lub niedostępny) 2 Removable Disk Dysk wymienny (np. pendrive, karta SD, zewnętrzny dysk USB) 3 Local Disk Dysk lokalny (np. klasyczny HDD lub SSD zamontowany w systemie) 4 Network Drive Dysk sieciowy (zamapowany udział sieciowy) 5 Compact Disk Napęd CD-ROM, DVD-ROM itp. 6 RAM Disk Dysk w pamięci operacyjnej (RAM disk, tworzony np. przez oprogramowanie)
Get-NetworkData – Konfiguracja sieciowa
function Get-NetworkData {
try {
Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration -Filter "IPEnabled=True" -ErrorAction Stop |
ForEach-Object {
[PSCustomObject]@{
'Adapter' = $_.Description
'Adres IP' = ($_.IPAddress -join ', ')
'Maska podsieci' = ($_.IPSubnet -join ', ')
'Brama domyślna' = if ($_.DefaultIPGateway) { ($_.DefaultIPGateway -join ', ') } else { "Brak" }
'Serwery DNS' = ($_.DNSServerSearchOrder -join ', ')
'DHCP włączone' = $_.DHCPEnabled
}
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o sieci: $_"
return @()
}
}
Co tu się dzieje?
- Dzięki przeszukaniu klasy
Win32_NetworkAdapterConfigurationjesteśmy w stanie znaleźć niezbędne dane ustawień kart sieciowych. - Za pomocą parametru
-Filter "IPEnabled=True"skrypt analizuje tylko te karty, które mają przypisany i aktywny protokół IP. - Podobnie jak poprzednio może się zdarzyć, że zaadresowanych kart sieciowych będzie więcej. W związku z tym ponownie cmdlet
ForEach-Objectw celu wyodrębnienia konkretnych parametrów dla każdej karty sieciowej. - Identycznie jak w przypadku dysków – konstruktor
[PSCustomObject]i przypisujemy konkretne wartości jak adres IP czy maska podsieci. - Czasami dany interfejs sieciowy będzie zaadresowany więcej niż jednym adresem (przykładowo IPv4 i IPv6) w związku z czym, aby jakoś to wyglądało, można wykorzystać operator
-join ', ', który łączy je w jeden ciąg znaków oddzielony przecinkami. - Instrukcja warunkowa
ifsprawdza, czy adapter ma zdefiniowaną bramę, aby uniknąć wyświetlania pustych wartości.
Get-LocalUsersData – Audyt kont użytkowników
function Get-LocalUsersData {
try {
Get-LocalUser -ErrorAction Stop |
ForEach-Object {
[PSCustomObject]@{
'Nazwa użytkownika' = $_.Name
'Włączone' = $_.Enabled
'Ostatnie logowanie' = if ($_.LastLogon) { $_.LastLogon } else { "Nigdy" }
'Wymagane hasło' = $_.PasswordRequired
}
}
}
catch {
Write-Error "Błąd podczas pobierania informacji o użytkownikach: $_"
return @()
}
}
Co tu się dzieje?
- Dla odmiany w ostatniej funkcji pomocniczej skorzystamy z cmdletu
Get-LocalUser. Dlaczego? Otóż po głębszej analizie okazuje się, że jest to cmdlet dostępny od PowerShell 5.1, który:- Jest szybszy niż WMI/CIM
- Zwraca bardziej szczegółowe informacje
- Ma lepszą obsługę błędów
- Następnie dane ponownie trafiają do
For-EachObjecti budujemyPSCustomObjectz konkretnymi Informacjami:Enabled– wyłączone konta powinny być usunięte lub monitorowaneLastLogon– konta nieużywane od długiego czasu to potencjalne ryzykoPasswordRequired– konta bez hasła to poważna dziura w bezpieczeństwie
Krok 3: Główna logika – Generowanie raportu
Ok, udało się. Po tym dość długim przeglądzie funkcji pomocniczych w końcu dotarliśmy do serca naszego projektu. Jak wspomniałem wcześniej, to tutaj odbywa się całe zbieranie informacji (wywołanie poszczególnych funkcji pomocniczych) oraz generacja raportu (wykorzystanie modułu PSWriteHTL). Jeżeli masz jeszcze siłę, to lecimy dalej.
Inicjalizacja i zbieranie danych
try {
Write-Host "Inicjalizacja modułu PSWriteHTML..." -ForegroundColor Cyan
Initialize-PSWriteHTML
Write-Host "Zbieranie informacji systemowych..." -ForegroundColor Cyan
$ComputerData = Get-ComputerData
$ProcessorData = Get-ProcessorData
$RamData = Get-MemoryData
$BIOSData = Get-BIOSData
$DiskData = Get-DiskData
$NetworkData = Get-NetworkData
$LocalUsersData = Get-LocalUsersData
Co tu się dzieje?
- Zaczynam od głównego bloku
try. - Cmdlet
Write-Hostz parametrem-ForegroundColor Cyaninformuje użytkownika o postępie. To dość istotne w przypadku skryptów długotrwałych. Dzięki temu użytkownik wie, że proces działa i coś się dzieje. - Następnie dane z poszczególnych funkcji pomocniczych są zbierane i przypisywane do konkretnych zmiennych. Alternatywą mogłoby być wywoływanie funkcji bezpośrednio w HTML, ale:
- Obecne podejście jest bardziej czytelne
- Łatwiej debugować (można sprawdzić każdą zmienną)
- Umożliwia dodatkowe przetwarzanie danych przed wyświetlaniem
Oczywiście w ramach własnego eksperymentu możesz wywołać funkcje bezpośrednio przy generowaniu raportu (daj znać, jak Ci poszło).
Struktura raportu
Teraz czas zabrać się za generację raportu. Za pierwszym razem może się to wydawać dość skomplikowane, ale mogę zapewnić Cię, że każdy następny raport będziesz już generował dużo szybciej. Moduł PSWriteHTML używa zagnieżdżonych sekcji oraz paneli do organizacji treści. Poniżej zamieszczam przykładowy schemat:
HTMLSection (główna)
└─ HTMLPanel (niewidoczny kontener)
└─ HTMLSection (urządzenie)
└─ HTMLPanel (niewidoczny kontener)
├─ HTMLSection (procesor)
└─ HTMLSection (BIOS)
└─ HTMLPanel (niewidoczny kontener)
├─ HTMLSection (sieć)
├─ HTMLSection (dyski)
└─ HTMLSection (RAM)
└─ HTMLPanel (niewidoczny kontener)
└─ HTMLSection (użytkownicy)
Raport podzielony będzie na kilka sekcji. Jedna główna skupiająca całość, następnie pozostałe ułożone obok siebie na przezroczystych (niewidzialnych) panelach. Jednakże, w pierwszej kolejności należy stworzyć plik strony za pomocą New-HTML:
New-HTML -TitleText "Raport systemowy - $env:COMPUTERNAME" -Online -FilePath $OutputPath {
Co tu się dzieje?
- Cmdlet
New-Htmlz modułuPSWriteHTMLodpowiada za wygenerowanie nowej strony naszego raportu. Polecenie uzupełniamy dodatkowymi parametrami:TitleText– tytuł strony widoczny w karcie przeglądarkiOnline– ładuje CSS i JavaScript z CDN (wymaga internetu przy otwieraniu raportu, ale zmniejsza rozmiar pliku)FilePath– ścieżka do zapisanego plikuScriptBlock{...}– zawiera całą treść raportu
💡Warto wiedzieć
Alternatywa dla
-Online: Można pominąć ten parametr – wtedy wszystkie zasoby będą embedded w HTML (plik będzie większy, ale działa offline).
Główna sekcja informacji
New-HTMLSection -HeaderText "Informacje o komputerze - $env:COMPUTERNAME" -HeaderBackGroundColor $Config.HeaderColor {
Co tu się dzieje?
- Kolejnym krokiem jest utworzenie głównej sekcji informacji, która będzie gromadziła wszystkie pozostałe. Jej tytuł to „Informacja o komputerze –
$env:COMPUTERNAME(gdzie zmienna środowiskowa automatycznie wskazuję nazwę komputera). - Kolejny parametr
-HeaderBackGroundColorodpowiedzialny jest za kolor nagłówka sekcji jego wartość określa zmienna$Config.HeaderColor
Dane dotyczące urządzenia
New-Panel -Invisible {
New-HTMLSection -HeaderText "Urządzenie" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $ComputerData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering
}
}
Co tu się dzieje?
- Aby ujednolicić wygląd raportu tworzę niewidzialny panel (cmdlet
New-Panel -Invisible), który grupuje elementy bez dodawania zbędnych ramek. - Następnie tworzę nową sekcję z nagłówkiem „Urządzenie”.
- Kolejny cmdlet
New-HTMLTableodpowiada za wygenerowanie tabeli na podstawie wartości parametru -DataTable. W tym konkretnym przypadku będzie to zmienna$ComputerData, która zawiera dane dotyczące typu, nazwy komputera, modelu itd. - Dodając serię parametrów – wyłączam wszystkie zbędne funkcje interaktywne, takie jak wyszukiwarka, sortowanie czy stronicowanie. Dzięki temu w otrzymam prostą, statyczną tabelę.
Parametry cmdletu New-HTMLTable:
DataTable– przekazuje dane (hashtable lub array obiektów)HideFooter– ukrywa stopkę tabeliHideButtons– ukrywa przyciski eksportu (CSV, Excel, PDF)DisableInfo– ukrywa „Showing 1 to 10 of 50 entries”DisablePaging– wyświetla wszystkie rekordy (bez stronicowania)DisableSearch– ukrywa pole wyszukiwaniaDisableOrdering– wyłącza sortowanie kolumn
Procesor oraz BIOS
New-HTMLPanel -Invisible {
New-HTMLSection -HeaderText "Procesor" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $ProcessorData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering
}
New-HTMLSection -HeaderText "BIOS" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $BIOSData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering
}
}
Co tu się dzieje?
- Podobnie jak poprzednio tworzę niewidoczną przestrzeń, tym razem jednak dla dwóch niezależnych sekcji tematycznych.
- Pierwsza sekcja: wyświetla dane techniczne dotyczące procesora (zmienna
$ProcessorData). - Druga sekcja: prezentuje informacje o oprogramowaniu układowym płyty głównej (zmienna
$BIOSData).
- Pierwsza sekcja: wyświetla dane techniczne dotyczące procesora (zmienna
- Obie sekcje korzystają z tego samego koloru nagłówka i mają wyłączone funkcje interaktywne. Dzięki czemu uzyskam minimalistyczny, raportowy wygląd.
💡Warto wiedzieć
Panel niewidoczny to kontener służący wyłącznie do layoutu. Umieszczone w nim sekcje będą wyświetlane obok siebie (float layout), a nie jedna pod drugą.
Kolejne sekcje – ustawienia sieci, dyski i pamięć RAM
New-HTMLPanel -Invisible {
New-HTMLSection -HeaderText "Ustawienia sieciowe" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $NetworkData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering
}
New-HTMLSection -HeaderText "Dyski" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $DiskData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering {
New-TableCondition -Name 'Wykorzystane (%)' -ComparisonType number -Operator gt -Value 90 -BackgroundColor Red -Color White
New-TableCondition -Name 'Wykorzystane (%)' -ComparisonType number -Operator gt -Value 75 -BackgroundColor Orange -Color White
New-TableCondition -Name 'Wykorzystane (%)' -ComparisonType number -Operator le -Value 75 -BackgroundColor LightGreen
}
}
New-HTMLSection -HeaderText "Pamięć (RAM)" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $RamData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering {
New-HTMLTableCondition -Name 'Wykorzystanie (%)' -ComparisonType number -Operator gt -Value 90 -BackgroundColor Red -Color White
New-HTMLTableCondition -Name 'Wykorzystanie (%)' -ComparisonType number -Operator gt -Value 75 -BackgroundColor Orange -Color White
}
}
}
Co się tutaj dzieje?
- Zaczynam od kolejnego niewidzialnego kontenera (panelu) ->
New-HTMLPanel -Invisible. - Następnie tworzę pierwszą sekcję „Ustawienia sieciowe”, gdzie umieszczam dane dotyczące kart sieciowych (
$NetworkData). Podobnie jak poprzednio unikam wszelkich dodatków, by otrzymać prosty, przejrzysty wygląd. - Druga sekcja odpowiada informacjom o przestrzeni dyskowej (
$DiskData). - Nowością jest zastosowanie polecenia
New-TableCondition, które odpowiada za zdefiniowanie warunku. W tym konkretnym wypadku sprawdzamy warunki dotyczące zajętości dysku. I tak:-Name 'Wykorzystane (%)'-ComparisonType number-Operator gt-Value 90-BackgroundColor Red -Color White
- Ostatnia sekcja w tym panelu prezentuje status pamięci operacyjnej komputera (
$RamData). Podobnie jak z dyskami, zastosowałem tutaj warunki, do wizualnej oceny zajętości pamięci.
💡Warto wiedzieć
Kolejność warunków ma znaczenie, gdyż są sprawdzane od góry do dołu. W naszym przykładzie załużmy, że mamy dysk z 95% wypełnienia. Wtedy:
- Pasuje do warunku 1, czyli zajętość > 90 → kolor: CZERWONY
- Pasuje też do warunku 2, gdzie zajętość >75 , ale został już sformatowany
Kolejna sprawa dotyczy ComparisonType number: Typ
numberzapewnia poprawne porównanie numeryczne (95 > 75), w przeciwieństwie dostring(gdzie „95” < „75” alfabetycznie).
Ostatnia sekcja – lokalni użytkownicy
New-HTMLPanel -Invisible {
New-HTMLSection -HeaderText "Lokalni użytkownicy" -HeaderBackGroundColor $Config.HeaderColor {
New-HTMLTable -DataTable $LocalUsersData -HideFooter -HideButtons -DisableInfo -DisablePaging -DisableSearch -DisableOrdering
}
}
Co się tutaj dzieje?
- Ostatnia sekcja raportu dotyczy kont lokalnych użytkowników.
- W zasadzie nic nowego -> stawiam niewidzialny panel, następnie sekcja i tabela z danymi dotyczącymi użytkowników.
Stopka raportu
New-HTMLFooter {
New-HTMLText -Text "Raport wygenerowany: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | Komputer: $env:COMPUTERNAME | Użytkownik: $env:USERNAME" -Alignment center
}
Co tu się dzieje?
- Na zakończenie raportu warto dodać informacje dotyczące czasu wygenerowania raportu oraz nazwy komputera i użytkownika, który raport ten wygenerował. Do tego celu idealnie nadaje się cmdlet
New-HTMLFooter, który odpowiada za utworzenie stopki. W tym przypadku w stopce umieszam:$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')– timestamp generowania raportu$env:COMPUTERNAME– nazwa komputera$env:USERNAME– użytkownik uruchamiający skrypt-Alignment center– wyśrodkowanie
Finalizacja i wyświetlenie
} -ShowHTML Write-Host "`nRaport został pomyślnie wygenerowany: $OutputPath" -ForegroundColor Green
Co się tutaj dzieje?
- Parametr
-ShowHTMLautomatycznie otwiera wygenerowany raport w domyślnej przeglądarce. Zachowanie to jest dość wygodne dla natychmiastowej weryfikacji. Jeżeli nie chcesz otwierać raportu zaraz po generacji, usuń ten parametr. - Kolejny element to komunikat sukcesu. Otrzymujamy tę informacje, jeżeli wszystko przebiegło pomyśle.
`n– znak nowej linii (dla czytelności)-ForegroundColor Green– zielony kolor = sukces (uniwersalna konwencja UX)
Obsługa błędów
}
catch {
Write-Error "Wystąpił błąd podczas generowania raportu: $_"
exit 1
}
Co tutaj się dzieje?
- Główna sekcja
catch, która przechwytuje wszystkie nieobsłużone wyjątki z blokutry. - Następnie komunikat o błędzie, gdzie zmienna
$_zawiera obiekt błędu. - Dodatkowo
exit 1, czyli kod wyjścia sygnalizujący błąd (standardowa konwencja Unix/Linux). Jest to istotne dla:- Skryptów automatyzacji (mogą sprawdzić
$LASTEXITCODE) - Systemów CI/CD
- Harmonogramów automatycznych (przykładowo Task Scheduler może reagować na kody błędów)
- Skryptów automatyzacji (mogą sprawdzić
Cały skrypt
Poniżej znajdziesz cały skrypt gotowy do skopiowania i wypróbowania (w komentarzu daj znać, jak Ci poszło).
Użytkowanie
Skrypt jest teraz gotowy do pracy. Możesz go uruchomić standardowo lub z własną ścieżką:
# Uruchom skrypt z domyślnymi parametrami .\Get-SystemReport.ps1 # Uruchom skrypt z własną ścieżką do zapisu raportu .\Get-SystemReport.ps1 -OutputPath "C:\Reports\$(Get-Date -Format 'yyyyMMdd')_$env:COMPUTERNAME.html"
Jeżeli wszystko przebiegło pomyślnie to po wykonaniu całości skryptu Twoim oczom powinien ukazać się obraz podobny do tego zamieszczonego poniżej.

Co dalej?
Ten miniprojekt to dopiero punkt wejścia w świat niesamowitych możliwości PowerShella. Jeżeli tylko znajdziesz czas i chęci to możesz go rozbudować o:
- Porównanie raportów z różnych dni – pokazuj zmiany między kolejnymi uruchomieniami
- Alerty – wysyłaj automatyczne powiadomienia/e-maile, gdy dysk czy RAM przekroczy 90% wykorzystania
- Integrację z Active Directory – zbieraj dane z wielu komputerów w domenie
- Eksport do bazy danych – przesyłaj dane w celu długoterminowej archiwizacji i analizy
- Dodatkowe możliwości – agreguj dane z całej infrastruktury: dziennik zdarzeń, serwisy, procesy, aplikacje itp
Zachęcam do eksperymentowania, dostosowywania i dzielenia się własnymi ulepszeniami!
Automatyzacja z Task Scheduler:
Możesz zaplanować codzienne generowanie raportów:
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\Scripts\Get-SystemReport.ps1 -OutputPath C:\Reports\Daily_$(Get-Date -Format 'yyyyMMdd').html" $Trigger = New-ScheduledTaskTrigger -Daily -At 9AM Register-ScheduledTask -TaskName "Daily System Report" -Action $Action -Trigger $Trigger
Co tu się dzieje?
- Na początek zmienna
$Actiondo której przypisuję cmdletNew-ScheduleTaskActionz parametrami:-Execute-Argument-OutputPath
- W nazwie pliku raportu dynamicznie generujesz datę wykonania w formacie
yyyyMMdd. - Definiujesz wyzwalacz uruchamiający zadanie codziennie o godzinie 9:00.
- Rejestrujesz zadanie w Harmonogramie zadań systemu Windows pod czytelną nazwą.
Podsumowanie
Automatyzacja raportowania stanu systemu to jeden z fundamentów dojrzałego podejścia do administracji IT. Zamiast reagować ad hoc i ręcznie zbierać informacje w sytuacjach awaryjnych, warto zbudować powtarzalny, przewidywalny mechanizm generowania raportów, który dostarcza aktualnych danych w ustandaryzowanej formie.
PowerShell w połączeniu z modułem PSWriteHTML pozwala stworzyć rozwiązanie, które jest jednocześnie elastyczne, czytelne i łatwe w dalszym rozwoju. Taki raport może pełnić rolę szybkiej diagnostyki w trakcie incydentu, materiału referencyjnego dla zespołu, a także elementu cyklicznego monitoringu stanu infrastruktury.
Przedstawiony skrypt to punkt wyjścia do budowy własnego systemu raportowania – w praktycznych wdrożeniach warto rozbudować go o obsługę wielu hostów, centralne składowanie raportów, harmonogram uruchomień oraz mechanizmy powiadomień. W ten sposób proste narzędzie administracyjne staje się realnym wsparciem dla codziennej pracy administratora i elementem większego procesu automatyzacji operacyjnej.
To wszystko na dziś!
Jeśli masz ciekawe spostrzeżenia lub doświadczenia w tym temacie – koniecznie podziel się nimi w komentarzach.
A jeśli moje materiały są dla Ciebie pomocne, możesz postawić mi wirtualną kawę.
Dzięki za wsparcie!


Adam Pietrzak
Administrator | Trener i autor szkoleń | Entuzjasta PowerShellaAdministrator systemów i sieci wsparcia działań wojskowych z ponad 10-letnim doświadczeniem. Praktyk w dziedzinie zarządzania Active Directory, bezpieczeństwa systemu Windows oraz automatyzacji zadań (PowerShell). Trener i twórca materiałów edukacyjnych (szkolenia, warsztaty, artykuły, podręczniki). Pasjonat dzielenia się wiedzą i wspierania początkujących administratorów IT. Prywatnie – amator aktywnego wypoczynku i rodzinnych podróży.

