DNS Server: The Internet’s Address Book

In the vast landscape of the internet, DNS servers play a crucial role in ensuring that we can easily access websites and online services. DNS, which stands for Domain Name System, acts as the internet’s address book, translating human-readable domain names into IP addresses that computers use to identify each other on the network.

What is a DNS Server?

A DNS server, also known as a name server, is a specialized computer that stores and manages a database of domain names and their corresponding IP addresses. When you type a website address into your browser, your device sends a query to a DNS server to resolve the domain name into an IP address.

How DNS Servers Work

The DNS resolution process typically involves several steps:

  1. Local DNS cache check: Your device first checks its local DNS cache to see if it has recently looked up the IP address for the requested domain.
  2. Recursive resolver: If not found locally, the query is sent to a recursive resolver, usually provided by your Internet Service Provider (ISP).
  3. Root nameservers: The recursive resolver then queries the root nameservers to find the authoritative nameservers for the top-level domain (e.g., .com, .org).
  4. Top-level domain nameservers: These servers provide information about the authoritative nameservers for the specific domain.
  5. Authoritative nameservers: Finally, the authoritative nameservers for the domain provide the IP address associated with the requested domain name.
  6. Response: The IP address is sent back through the chain to your device, which can then establish a connection with the website’s server.

Types of DNS Servers

  1. Recursive resolvers: These servers handle requests from clients and query other DNS servers to find the requested information.
  2. Root nameservers: There are 13 sets of root nameservers distributed worldwide, serving as the top of the DNS hierarchy.
  3. TLD nameservers: These servers are responsible for top-level domains like .com, .org, and country-code TLDs.
  4. Authoritative nameservers: These servers hold the actual DNS records for specific domains.

Importance of DNS Servers

DNS servers are critical to the functioning of the internet for several reasons:

  1. User-friendly navigation: They allow users to access websites using easy-to-remember domain names instead of numerical IP addresses.
  2. Load balancing: DNS can be used to distribute traffic across multiple servers, improving website performance and reliability.
  3. Email routing: DNS is essential for email delivery, directing messages to the correct mail servers.
  4. Security: DNS can help protect against various online threats through features like DNSSEC (DNS Security Extensions).

DNS Server Security and Performance

To ensure optimal performance and security, organizations often implement the following measures:

  1. DNS caching: Storing frequently requested DNS information to reduce lookup times.
  2. Anycast DNS: Using multiple servers with the same IP address to improve response times and resilience.
  3. DNSSEC: Implementing cryptographic signatures to verify the authenticity of DNS responses.
  4. DNS filtering: Blocking access to malicious domains to protect users from phishing and malware.

DNS servers are the unsung heroes of the internet, working behind the scenes to make our online experiences seamless and user-friendly. As the internet continues to evolve, the role of DNS servers in maintaining a fast, secure, and reliable online ecosystem remains more important than ever.

Local Windows Client Audit Toolkit for Domain Computers

<#
.SYNOPSIS
Local Windows Client Audit Toolkit for Domain Computers

.DESCRIPTION
This script performs a comprehensive audit of a local Windows client that is part of a domain environment.
It checks various system settings, security configurations, and domain-related information.

.NOTES
File Name      : LocalWindowsClientAuditToolkit.ps1
Author         : [Your Name]
Prerequisite   : PowerShell V5.1 or later, administrator rights on the local machine
Version        : 1.0
Date           : [Current Date]

.EXAMPLE
.\LocalWindowsClientAuditToolkit.ps1
#>

# Global variables
$global:reportPath = "$env:USERPROFILE\Desktop\Windows_Client_Audit_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"

function Show-Menu {
    Clear-Host
    Write-Host "=== Local Windows Client Audit Toolkit ===" -ForegroundColor Cyan
    Write-Host "1. System Information"
    Write-Host "2. Domain Information"
    Write-Host "3. Local User Accounts"
    Write-Host "4. Installed Software"
    Write-Host "5. Windows Update Status"
    Write-Host "6. Security Settings"
    Write-Host "7. Network Configuration"
    Write-Host "8. Shared Folders"
    Write-Host "9. Scheduled Tasks"
    Write-Host "10. Generate Comprehensive HTML Report"
    Write-Host "11. Exit"
}

function Get-SystemInformation {
    Write-Host "`nGathering System Information..." -ForegroundColor Yellow
    $os = Get-WmiObject Win32_OperatingSystem
    $cs = Get-WmiObject Win32_ComputerSystem
    $bios = Get-WmiObject Win32_BIOS

    $result = [PSCustomObject]@{
        ComputerName = $env:COMPUTERNAME
        OSName = $os.Caption
        OSVersion = $os.Version
        OSArchitecture = $os.OSArchitecture
        Manufacturer = $cs.Manufacturer
        Model = $cs.Model
        BIOSVersion = $bios.SMBIOSBIOSVersion
        LastBootUpTime = $os.ConvertToDateTime($os.LastBootUpTime)
        InstallDate = $os.ConvertToDateTime($os.InstallDate)
    }

    $result | Format-List
    return $result
}

function Get-DomainInformation {
    Write-Host "`nGathering Domain Information..." -ForegroundColor Yellow
    $domain = Get-WmiObject Win32_ComputerSystem
    $adInfo = Get-WmiObject Win32_NTDomain

    $result = [PSCustomObject]@{
        DomainName = $domain.Domain
        PartOfDomain = $domain.PartOfDomain
        DomainRole = switch ($domain.DomainRole) {
            0 {"Standalone Workstation"}
            1 {"Member Workstation"}
            2 {"Standalone Server"}
            3 {"Member Server"}
            4 {"Backup Domain Controller"}
            5 {"Primary Domain Controller"}
        }
        DomainController = $adInfo.DomainControllerName
        DomainControllerAddress = $adInfo.DomainControllerAddress
    }

    $result | Format-List
    return $result
}

function Get-LocalUserAccounts {
    Write-Host "`nGathering Local User Account Information..." -ForegroundColor Yellow
    $users = Get-WmiObject Win32_UserAccount -Filter "LocalAccount=True"
    $results = @()

    foreach ($user in $users) {
        $results += [PSCustomObject]@{
            Username = $user.Name
            FullName = $user.FullName
            Disabled = $user.Disabled
            PasswordRequired = $user.PasswordRequired
            PasswordChangeable = $user.PasswordChangeable
            PasswordExpires = $user.PasswordExpires
        }
    }

    $results | Format-Table -AutoSize
    return $results
}

function Get-InstalledSoftware {
    Write-Host "`nGathering Installed Software Information..." -ForegroundColor Yellow
    $software = Get-WmiObject Win32_Product | Select-Object Name, Version, Vendor, InstallDate
    $software | Format-Table -AutoSize
    return $software
}

function Get-WindowsUpdateStatus {
    Write-Host "`nChecking Windows Update Status..." -ForegroundColor Yellow
    $updateSession = New-Object -ComObject Microsoft.Update.Session
    $updateSearcher = $updateSession.CreateUpdateSearcher()
    $pendingUpdates = $updateSearcher.Search("IsInstalled=0")

    $result = [PSCustomObject]@{
        PendingUpdatesCount = $pendingUpdates.Updates.Count
        LastUpdateDate = (Get-HotFix | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1).InstalledOn
    }

    $result | Format-List
    return $result
}

function Get-SecuritySettings {
    Write-Host "`nGathering Security Settings..." -ForegroundColor Yellow
    $firewallStatus = Get-NetFirewallProfile | Select-Object Name, Enabled
    $avProduct = Get-WmiObject -Namespace root\SecurityCenter2 -Class AntiVirusProduct
    $uac = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" -Name "EnableLUA"

    $result = [PSCustomObject]@{
        FirewallStatus = $firewallStatus
        AntiVirusProduct = $avProduct.displayName
        UACEnabled = $uac.EnableLUA -eq 1
    }

    $result | Format-List
    return $result
}

function Get-NetworkConfiguration {
    Write-Host "`nGathering Network Configuration..." -ForegroundColor Yellow
    $adapters = Get-WmiObject Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled -eq $true }
    $results = @()

    foreach ($adapter in $adapters) {
        $results += [PSCustomObject]@{
            AdapterName = $adapter.Description
            IPAddress = $adapter.IPAddress -join ", "
            SubnetMask = $adapter.IPSubnet -join ", "
            DefaultGateway = $adapter.DefaultIPGateway -join ", "
            DNSServers = $adapter.DNSServerSearchOrder -join ", "
            MACAddress = $adapter.MACAddress
        }
    }

    $results | Format-Table -AutoSize
    return $results
}

function Get-SharedFolders {
    Write-Host "`nGathering Shared Folder Information..." -ForegroundColor Yellow
    $shares = Get-WmiObject Win32_Share
    $results = @()

    foreach ($share in $shares) {
        $results += [PSCustomObject]@{
            Name = $share.Name
            Path = $share.Path
            Description = $share.Description
            Type = switch ($share.Type) {
                0 {"Disk Drive"}
                1 {"Print Queue"}
                2 {"Device"}
                3 {"IPC"}
                2147483648 {"Disk Drive Admin"}
                2147483649 {"Print Queue Admin"}
                2147483650 {"Device Admin"}
                2147483651 {"IPC Admin"}
            }
        }
    }

    $results | Format-Table -AutoSize
    return $results
}

function Get-ScheduledTasks {
    Write-Host "`nGathering Scheduled Task Information..." -ForegroundColor Yellow
    $tasks = Get-ScheduledTask | Where-Object {$_.State -ne "Disabled"}
    $results = @()

    foreach ($task in $tasks) {
        $results += [PSCustomObject]@{
            TaskName = $task.TaskName
            State = $task.State
            LastRunTime = $task.LastRunTime
            NextRunTime = $task.NextRunTime
            Author = $task.Author
        }
    }

    $results | Format-Table -AutoSize
    return $results
}

function Generate-HTMLReport {
    param([hashtable]$AllResults)

    Write-Host "`nGenerating Comprehensive HTML Report..." -ForegroundColor Yellow
    $reportContent = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Windows Client Audit Report</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 20px; }
        h1, h2, h3 { color: #0078D4; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .warning { color: orange; }
        .critical { color: red; }
    </style>
</head>
<body>
    <h1>Windows Client Audit Report</h1>
    <p>Generated on: $(Get-Date)</p>

    <h2>System Information</h2>
    $($AllResults.SystemInfo | ConvertTo-Html -Fragment)

    <h2>Domain Information</h2>
    $($AllResults.DomainInfo | ConvertTo-Html -Fragment)

    <h2>Local User Accounts</h2>
    $($AllResults.LocalUsers | ConvertTo-Html -Fragment)

    <h2>Installed Software</h2>
    $($AllResults.InstalledSoftware | ConvertTo-Html -Fragment)

    <h2>Windows Update Status</h2>
    $($AllResults.WindowsUpdateStatus | ConvertTo-Html -Fragment)

    <h2>Security Settings</h2>
    $($AllResults.SecuritySettings | ConvertTo-Html -Fragment)

    <h2>Network Configuration</h2>
    $($AllResults.NetworkConfig | ConvertTo-Html -Fragment)

    <h2>Shared Folders</h2>
    $($AllResults.SharedFolders | ConvertTo-Html -Fragment)

    <h2>Scheduled Tasks</h2>
    $($AllResults.ScheduledTasks | ConvertTo-Html -Fragment)
</body>
</html>
"@

    $reportContent | Out-File -FilePath $global:reportPath
    Write-Host "Report generated and saved to: $global:reportPath" -ForegroundColor Green
}

# Main program loop
$allResults = @{}

do {
    Show-Menu
    $choice = Read-Host "`nEnter your choice (1-11)"

    switch ($choice) {
        "1" { $allResults.SystemInfo = Get-SystemInformation }
        "2" { $allResults.DomainInfo = Get-DomainInformation }
        "3" { $allResults.LocalUsers = Get-LocalUserAccounts }
        "4" { $allResults.InstalledSoftware = Get-InstalledSoftware }
        "5" { $allResults.WindowsUpdateStatus = Get-WindowsUpdateStatus }
        "6" { $allResults.SecuritySettings = Get-SecuritySettings }
        "7" { $allResults.NetworkConfig = Get-NetworkConfiguration }
        "8" { $allResults.SharedFolders = Get-SharedFolders }
        "9" { $allResults.ScheduledTasks = Get-ScheduledTasks }
        "10" { Generate-HTMLReport -AllResults $allResults }
        "11" { Write-Host "Exiting program..." -ForegroundColor Yellow; break }
        default { Write-Host "Invalid choice. Please try again." -ForegroundColor Red }
    }

    if ($choice -ne "11") {
        Read-Host "`nPress Enter to continue..."
    }
} while ($choice -ne "11")

This Local Windows Client Audit Toolkit for Domain Computers includes:

  1. A menu-driven interface for easy navigation.
  2. Functions to gather various aspects of the local Windows client:
    • System Information
    • Domain Information
    • Local User Accounts
    • Installed Software
    • Windows Update Status
    • Security Settings
    • Network Configuration
    • Shared Folders
    • Scheduled Tasks
  3. HTML report generation for easy sharing and viewing of results.

Key features:

  • Comprehensive system information gathering
  • Domain-specific information for domain-joined computers
  • Local user account analysis
  • Software inventory
  • Windows Update status check
  • Basic security settings review (firewall, antivirus, UAC)
  • Network configuration details
  • Shared folder enumeration
  • Active scheduled tasks listing

This tool is particularly useful for:

  • IT administrators performing audits on domain-joined computers
  • Security professionals assessing the configuration of Windows clients
  • Help desk personnel gathering system information for troubleshooting
  • Anyone needing to quickly collect comprehensive information about a Windows client in a domain environment

To use this script effectively:

  1. Run PowerShell as an administrator on the Windows client you want to audit
  2. Ensure you have the necessary permissions to query system information
  3. Review the generated HTML report for a comprehensive overview of the client’s configuration

This script provides a thorough audit of a Windows client, helping to identify potential issues, misconfigurations, or security concerns in a domain environment. It’s designed to be run locally on the machine being audited, making it suitable for situations where remote access might be limited or restricted.

Dr. Scripto and the DNS Dilemma

It was a quiet Friday afternoon at the PowerShell Academy when suddenly, the tranquility was shattered by a frantic knock on Dr. Scripto’s office door.

“Come in!” Dr. Scripto called, looking up from his latest PowerShell module creation.

In burst Timmy, the academy’s junior network administrator, his face pale with panic. “Dr. Scripto! We have a major crisis! The academy’s DNS server is acting up, and nobody can access any websites or resources!”

Dr. Scripto’s eyes lit up with excitement. “A DNS emergency, you say? This sounds like a job for PowerShell!”

He grabbed his trusty laptop, adorned with PowerShell stickers, and followed Timmy to the server room. The place was in chaos, with blinking lights and the sound of frustrated users echoing through the halls.

“Let’s see what we’re dealing with,” Dr. Scripto said, cracking his knuckles and opening a PowerShell console.

First, he checked the DNS service status:

Get-Service -Name DNS | Select-Object Name, Status

“Hmm, the service is running. Let’s dig deeper,” Dr. Scripto muttered.

He then proceeded to check the DNS server’s configuration:

Get-DnsServerSetting -All | Format-List

“Aha!” Dr. Scripto exclaimed. “It seems our forwarders are misconfigured. Let’s fix that!”

He swiftly typed out a command to set the correct forwarders:

Set-DnsServerForwarder -IPAddress "8.8.8.8", "8.8.4.4"

“Now, let’s check our zone transfers,” Dr. Scripto said, his fingers flying across the keyboard:

Get-DnsServerZone | Where-Object {$_.ZoneType -eq "Primary"} | 
    Select-Object ZoneName, ZoneType, IsDsIntegrated, ReplicationScope

“Just as I suspected,” he murmured. “Our zone replication is off. Time to fix it!”

He quickly wrote a script to correct the zone replication:

$zones = Get-DnsServerZone | Where-Object {$_.ZoneType -eq "Primary" -and -not $_.IsDsIntegrated}
foreach ($zone in $zones) {
    Set-DnsServerPrimaryZone -Name $zone.ZoneName -ReplicationScope "Domain" -PassThru
}

As the script ran, the blinking lights on the server rack began to stabilize. Timmy watched in awe as Dr. Scripto worked his PowerShell magic.

“Now, for the final touch,” Dr. Scripto said with a flourish. “Let’s create a monitoring script to alert us if this happens again.”

He quickly wrote out a PowerShell script:

$scriptBlock = {
    $dnsService = Get-Service -Name DNS
    $forwarders = Get-DnsServerForwarder
    if ($dnsService.Status -ne 'Running' -or $forwarders.Count -eq 0) {
        Send-MailMessage -To "admin@powershellacademy.com" -From "monitor@powershellacademy.com" -Subject "DNS Alert!" -Body "Check DNS server immediately!"
    }
}

$trigger = New-JobTrigger -Once -At (Get-Date) -RepeatIndefinitely -RepetitionInterval (New-TimeSpan -Minutes 5)
Register-ScheduledJob -Name "DNSMonitor" -ScriptBlock $scriptBlock -Trigger $trigger

“There!” Dr. Scripto said triumphantly. “Now we’ll be alerted if anything goes wrong with our DNS server.”

Timmy’s eyes were wide with admiration. “Dr. Scripto, that was amazing! How did you know exactly what to do?”

Dr. Scripto chuckled, adjusting his PowerShell-themed bowtie. “My dear Timmy, in the world of IT, DNS is always the culprit until proven otherwise. And with PowerShell, we have the tools to prove it – and fix it – faster than you can say ‘cmdlet’!”

As they walked back to Dr. Scripto’s office, cheers erupted from around the academy as students and staff regained their internet access.

“Remember, Timmy,” Dr. Scripto said sagely, “in the face of any IT crisis, keep calm and PowerShell on!”

And with that, another DNS crisis was averted, thanks to the power of PowerShell and the wisdom of Dr. Scripto. As for Timmy, he headed straight to the library, determined to study more about DNS and PowerShell, inspired by the day’s adventure.

NETLOGON Analyzer Tool

<#
.SYNOPSIS
NETLOGON Analyzer Tool

.DESCRIPTION
This script analyzes NETLOGON-related issues, log files, and configurations across Domain Controllers
in an Active Directory environment.

.NOTES
File Name      : NETLOGONAnalyzer.ps1
Author         : [Your Name]
Prerequisite   : PowerShell V5.1 or later, Active Directory module, and appropriate AD permissions
Version        : 1.0
Date           : [Current Date]

.EXAMPLE
.\NETLOGONAnalyzer.ps1
#>

# Import required modules
Import-Module ActiveDirectory

# Global variables
$global:reportPath = "$env:USERPROFILE\Desktop\NETLOGON_Analysis_Report_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"

<#
.SYNOPSIS
Displays the main menu of the tool.
#>
function Show-Menu {
    Clear-Host
    Write-Host "=== NETLOGON Analyzer Tool ===" -ForegroundColor Cyan
    Write-Host "1. Check NETLOGON Service Status"
    Write-Host "2. Analyze NETLOGON Log Files"
    Write-Host "3. Verify NETLOGON Share Accessibility"
    Write-Host "4. Check NETLOGON Registry Settings"
    Write-Host "5. Analyze NETLOGON-related Group Policies"
    Write-Host "6. Check Secure Channel Status"
    Write-Host "7. Analyze NETLOGON Performance Counters"
    Write-Host "8. Generate Comprehensive HTML Report"
    Write-Host "9. Exit"
}

<#
.SYNOPSIS
Checks NETLOGON service status on all Domain Controllers.

.OUTPUTS
Array of PSObjects containing NETLOGON service status for each DC.
#>
function Check-NETLOGONServiceStatus {
    Write-Host "`nChecking NETLOGON Service Status..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $serviceStatus = @()
    foreach ($dc in $dcs) {
        $status = Get-Service -ComputerName $dc.HostName -Name "Netlogon" -ErrorAction SilentlyContinue
        $serviceStatus += [PSCustomObject]@{
            DomainController = $dc.HostName
            Status = if ($status) { $status.Status } else { "Unable to retrieve" }
            StartType = if ($status) { $status.StartType } else { "Unknown" }
        }
    }
    $serviceStatus | Format-Table -AutoSize
    return $serviceStatus
}

<#
.SYNOPSIS
Analyzes NETLOGON log files on all Domain Controllers.

.OUTPUTS
Array of PSObjects containing NETLOGON log analysis for each DC.
#>
function Analyze-NETLOGONLogFiles {
    Write-Host "`nAnalyzing NETLOGON Log Files..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $logAnalysis = @()
    foreach ($dc in $dcs) {
        $logPath = "\\$($dc.HostName)\c$\Windows\debug\netlogon.log"
        if (Test-Path $logPath) {
            $logContent = Get-Content $logPath -Tail 100
            $errorCount = ($logContent | Select-String -Pattern "ERROR" -CaseSensitive).Count
            $warningCount = ($logContent | Select-String -Pattern "WARNING" -CaseSensitive).Count
            $recentErrors = $logContent | Select-String -Pattern "ERROR" -CaseSensitive | Select-Object -Last 5

            $logAnalysis += [PSCustomObject]@{
                DomainController = $dc.HostName
                ErrorCount = $errorCount
                WarningCount = $warningCount
                RecentErrors = $recentErrors -join "`n"
            }
        } else {
            $logAnalysis += [PSCustomObject]@{
                DomainController = $dc.HostName
                ErrorCount = "N/A"
                WarningCount = "N/A"
                RecentErrors = "Unable to access log file"
            }
        }
    }
    $logAnalysis | Format-Table -AutoSize
    return $logAnalysis
}

<#
.SYNOPSIS
Verifies NETLOGON share accessibility on all Domain Controllers.

.OUTPUTS
Array of PSObjects containing NETLOGON share accessibility status for each DC.
#>
function Verify-NETLOGONShareAccessibility {
    Write-Host "`nVerifying NETLOGON Share Accessibility..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $shareStatus = @()
    foreach ($dc in $dcs) {
        $sharePath = "\\$($dc.HostName)\NETLOGON"
        $accessible = Test-Path $sharePath -ErrorAction SilentlyContinue
        $shareStatus += [PSCustomObject]@{
            DomainController = $dc.HostName
            ShareAccessible = $accessible
            SharePath = $sharePath
        }
    }
    $shareStatus | Format-Table -AutoSize
    return $shareStatus
}

<#
.SYNOPSIS
Checks NETLOGON-related registry settings on all Domain Controllers.

.OUTPUTS
Array of PSObjects containing NETLOGON registry settings for each DC.
#>
function Check-NETLOGONRegistrySettings {
    Write-Host "`nChecking NETLOGON Registry Settings..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $registrySettings = @()
    foreach ($dc in $dcs) {
        $settings = Invoke-Command -ComputerName $dc.HostName -ScriptBlock {
            $netlogonParams = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters" -ErrorAction SilentlyContinue
            return @{
                MaximumLogFileSize = $netlogonParams.MaximumLogFileSize
                DebugFlag = $netlogonParams.DbFlag
                ScavengeInterval = $netlogonParams.ScavengeInterval
            }
        } -ErrorAction SilentlyContinue

        $registrySettings += [PSCustomObject]@{
            DomainController = $dc.HostName
            MaxLogFileSize = if ($settings) { $settings.MaximumLogFileSize } else { "Unable to retrieve" }
            DebugFlag = if ($settings) { $settings.DebugFlag } else { "Unable to retrieve" }
            ScavengeInterval = if ($settings) { $settings.ScavengeInterval } else { "Unable to retrieve" }
        }
    }
    $registrySettings | Format-Table -AutoSize
    return $registrySettings
}

<#
.SYNOPSIS
Analyzes NETLOGON-related Group Policies.

.OUTPUTS
Array of PSObjects containing NETLOGON-related Group Policy settings.
#>
function Analyze-NETLOGONGroupPolicies {
    Write-Host "`nAnalyzing NETLOGON-related Group Policies..." -ForegroundColor Yellow
    $gpos = Get-GPO -All | Where-Object { $_.DisplayName -like "*NETLOGON*" -or $_.DisplayName -like "*Secure Channel*" }
    $gpAnalysis = @()
    foreach ($gpo in $gpos) {
        $report = Get-GPOReport -Guid $gpo.Id -ReportType XML
        $netlogonSettings = ([xml]$report).GPO.Computer.ExtensionData.Extension.Policy | 
            Where-Object { $_.Name -like "*NETLOGON*" -or $_.Name -like "*Secure Channel*" }
        
        foreach ($setting in $netlogonSettings) {
            $gpAnalysis += [PSCustomObject]@{
                GPOName = $gpo.DisplayName
                PolicyName = $setting.Name
                PolicyState = $setting.State
            }
        }
    }
    $gpAnalysis | Format-Table -AutoSize
    return $gpAnalysis
}

<#
.SYNOPSIS
Checks Secure Channel status for all Domain Controllers.

.OUTPUTS
Array of PSObjects containing Secure Channel status for each DC.
#>
function Check-SecureChannelStatus {
    Write-Host "`nChecking Secure Channel Status..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $secureChannelStatus = @()
    foreach ($dc in $dcs) {
        $status = Test-ComputerSecureChannel -Server $dc.HostName -ErrorAction SilentlyContinue
        $secureChannelStatus += [PSCustomObject]@{
            DomainController = $dc.HostName
            SecureChannelStatus = if ($status) { "Healthy" } else { "Unhealthy" }
        }
    }
    $secureChannelStatus | Format-Table -AutoSize
    return $secureChannelStatus
}

<#
.SYNOPSIS
Analyzes NETLOGON-related performance counters on all Domain Controllers.

.OUTPUTS
Array of PSObjects containing NETLOGON performance counter data for each DC.
#>
function Analyze-NETLOGONPerformanceCounters {
    Write-Host "`nAnalyzing NETLOGON Performance Counters..." -ForegroundColor Yellow
    $dcs = Get-ADDomainController -Filter *
    $perfCounters = @()
    foreach ($dc in $dcs) {
        $counters = Invoke-Command -ComputerName $dc.HostName -ScriptBlock {
            $netlogonCounters = Get-Counter -Counter @(
                "\Netlogon(*)\*"
            ) -ErrorAction SilentlyContinue
            return $netlogonCounters.CounterSamples | Select-Object Path, CookedValue
        } -ErrorAction SilentlyContinue

        if ($counters) {
            $perfCounters += [PSCustomObject]@{
                DomainController = $dc.HostName
                Semaphore_Acquires = ($counters | Where-Object { $_.Path -like "*Semaphore Acquires*" }).CookedValue
                Semaphore_Timeouts = ($counters | Where-Object { $_.Path -like "*Semaphore Timeouts*" }).CookedValue
                Semaphore_Holders = ($counters | Where-Object { $_.Path -like "*Semaphore Holders*" }).CookedValue
                Semaphore_Waiters = ($counters | Where-Object { $_.Path -like "*Semaphore Waiters*" }).CookedValue
            }
        } else {
            $perfCounters += [PSCustomObject]@{
                DomainController = $dc.HostName
                Semaphore_Acquires = "Unable to retrieve"
                Semaphore_Timeouts = "Unable to retrieve"
                Semaphore_Holders = "Unable to retrieve"
                Semaphore_Waiters = "Unable to retrieve"
            }
        }
    }
    $perfCounters | Format-Table -AutoSize
    return $perfCounters
}

<#
.SYNOPSIS
Generates a comprehensive HTML report of all analyses.

.PARAMETER AllResults
Hashtable containing all analysis results.

.OUTPUTS
Saves an HTML report to the desktop.
#>
function Generate-HTMLReport {
    param([hashtable]$AllResults)

    Write-Host "`nGenerating Comprehensive HTML Report..." -ForegroundColor Yellow
    $reportContent = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NETLOGON Analysis Report</title>
    <style>
        body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 1200px; margin: 0 auto; padding: 20px; }
        h1, h2, h3 { color: #0078D4; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>NETLOGON Analysis Report</h1>
    <p>Generated on: $(Get-Date)</p>

    <h2>NETLOGON Service Status</h2>
    $($AllResults.ServiceStatus | ConvertTo-Html -Fragment)

    <h2>NETLOGON Log File Analysis</h2>
    $($AllResults.LogAnalysis | ConvertTo-Html -Fragment)

    <h2>NETLOGON Share Accessibility</h2>
    $($AllResults.ShareStatus | ConvertTo-Html -Fragment)

    <h2>NETLOGON Registry Settings</h2>
    $($AllResults.RegistrySettings | ConvertTo-Html -Fragment)

    <h2>NETLOGON-related Group Policies</h2>
    $($AllResults.GPAnalysis | ConvertTo-Html -Fragment)

    <h2>Secure Channel Status</h2>
    $($AllResults.SecureChannelStatus | ConvertTo-Html -Fragment)

    <h2>NETLOGON Performance Counters</h2>
    $($AllResults.PerfCounters | ConvertTo-Html -Fragment)
</body>
</html>
"@

    $reportContent | Out-File -FilePath $global:reportPath
    Write-Host "Report generated and saved to: $global:reportPath" -ForegroundColor Green
}

# Main program loop
$allResults = @{}

do {
    Show-Menu
    $choice = Read-Host "`nEnter your choice (1-9)"

    switch ($choice) {
        "1" { $allResults.ServiceStatus = Check-NETLOGONServiceStatus }
        "2" { $allResults.LogAnalysis = Analyze-NETLOGONLogFiles }
        "3" { $allResults.ShareStatus = Verify-NETLOGONShareAccessibility }
        "4" { $allResults.RegistrySettings = Check-NETLOGONRegistrySettings }
        "5" { $allResults.GPAnalysis = Analyze-NETLOGONGroupPolicies }
        "6" { $allResults.SecureChannelStatus = Check-SecureChannelStatus }
        "7" { $allResults.PerfCounters = Analyze-NETLOGONPerformanceCounters }
        "8" { Generate-HTMLReport -AllResults $allResults }
        "9" { Write-Host "Exiting program..." -ForegroundColor Yellow; break }
        default { Write-Host "Invalid choice. Please try again." -ForegroundColor Red }
    }

    if ($choice -ne "9") {
        Read-Host "`nPress Enter to continue..."
    }
} while ($choice -ne "9")

This NETLOGON Analyzer Tool includes:

  1. A menu-driven interface for easy navigation.
  2. Functions to analyze various aspects of NETLOGON:
    • NETLOGON Service Status check
    • NETLOGON Log File analysis
    • NETLOGON Share accessibility verification
    • NETLOGON Registry Settings check
    • NETLOGON-related Group Policy analysis
    • Secure Channel status check
    • NETLOGON Performance Counter analysis
  3. Comprehensive error handling for each analysis function.
  4. A function to generate an HTML report of all collected data.

Key features:

  • Detailed analysis of NETLOGON service status across all Domain Controllers
  • Examination of NETLOGON log files for errors and warnings
  • Verification of NETLOGON share accessibility
  • Analysis of NETLOGON-related registry settings
  • Review of Group Policies affecting NETLOGON
  • Checking Secure Channel status for each DC
  • Analysis of NETLOGON performance counters
  • HTML report generation for easy sharing and viewing of results

This tool is particularly useful for:

  • Active Directory administrators
  • System administrators troubleshooting NETLOGON issues
  • IT professionals performing AD health checks
  • Security teams auditing AD configurations

To use this script effectively:

  1. Run PowerShell as an administrator
  2. Ensure you have the necessary permissions to query Domain Controllers and access NETLOGON-related information
  3. Have the Active Directory PowerShell module installed

This script provides a comprehensive overview of NETLOGON-related configurations and potential issues across an Active Directory environment. It can significantly streamline the process of troubleshooting NETLOGON problems and maintaining a healthy AD infrastructure.

Simple Network Diagnostics Tool

# Simple Network Diagnostics Tool

function Show-Menu {
    Clear-Host
    Write-Host "=== Simple Network Diagnostics Tool ===" -ForegroundColor Cyan
    Write-Host "1. Ping a host"
    Write-Host "2. Check DNS resolution"
    Write-Host "3. View IP configuration"
    Write-Host "4. Test internet connectivity"
    Write-Host "5. Exit"
}

function Ping-Host {
    $host_name = Read-Host "Enter the host name or IP address to ping"
    Write-Host "`nPinging $host_name..." -ForegroundColor Yellow
    ping $host_name
}

function Check-DNS {
    $dns_name = Read-Host "Enter the domain name to resolve"
    Write-Host "`nResolving $dns_name..." -ForegroundColor Yellow
    nslookup $dns_name
}

function View-IPConfig {
    Write-Host "`nIP Configuration:" -ForegroundColor Yellow
    ipconfig /all
}

function Test-Internet {
    Write-Host "`nTesting internet connectivity..." -ForegroundColor Yellow
    $result = Test-Connection -ComputerName "www.google.com" -Count 2 -Quiet
    if ($result) {
        Write-Host "Internet is accessible." -ForegroundColor Green
    } else {
        Write-Host "Internet is not accessible." -ForegroundColor Red
    }
}

do {
    Show-Menu
    $choice = Read-Host "`nEnter your choice (1-5)"

    switch ($choice) {
        "1" { Ping-Host }
        "2" { Check-DNS }
        "3" { View-IPConfig }
        "4" { Test-Internet }
        "5" { Write-Host "Exiting program..." -ForegroundColor Yellow; break }
        default { Write-Host "Invalid choice. Please try again." -ForegroundColor Red }
    }

    if ($choice -ne "5") {
        Read-Host "`nPress Enter to continue..."
    }
} while ($choice -ne "5")

This simplified Network Diagnostics Tool includes:

  1. A concise menu with 5 options
  2. Basic functions for essential network diagnostic tasks:
    • Pinging a host
    • Checking DNS resolution
    • Viewing IP configuration
    • Testing internet connectivity
  3. Use of simple PowerShell cmdlets and system commands
  4. Minimal error handling
  5. Easy-to-read output

This script provides a straightforward set of tools for basic network diagnostics and troubleshooting. It’s suitable for beginners or for quick checks of common network issues. The tool uses familiar commands like ping, nslookup, and ipconfig, which are widely recognized and easy to interpret.

This simplified version is more accessible for users who need to perform basic network diagnostics without the complexity of more advanced features.

System Information Gatherer

Description:

This PowerShell script is designed for beginners to learn how to gather and display basic system information. It demonstrates how to use various PowerShell cmdlets to retrieve information about the computer’s hardware, operating system, and current user. The script also introduces basic formatting techniques to present the information in a readable manner.

Full Script:

# System Information Gatherer

# Function to get and display system information
function Get-SystemInfo {
    Clear-Host
    Write-Host "=== System Information Gatherer ===" -ForegroundColor Cyan

    # Get computer system information
    $computerSystem = Get-CimInstance CIM_ComputerSystem
    $operatingSystem = Get-CimInstance CIM_OperatingSystem

    # Display basic system information
    Write-Host "`nComputer Name: " -NoNewline -ForegroundColor Yellow
    Write-Host $computerSystem.Name

    Write-Host "Manufacturer: " -NoNewline -ForegroundColor Yellow
    Write-Host $computerSystem.Manufacturer

    Write-Host "Model: " -NoNewline -ForegroundColor Yellow
    Write-Host $computerSystem.Model

    Write-Host "Operating System: " -NoNewline -ForegroundColor Yellow
    Write-Host $operatingSystem.Caption

    Write-Host "OS Version: " -NoNewline -ForegroundColor Yellow
    Write-Host $operatingSystem.Version

    # Get and display CPU information
    $processor = Get-CimInstance CIM_Processor
    Write-Host "`nCPU Information:" -ForegroundColor Green
    Write-Host "Name: $($processor.Name)"
    Write-Host "Cores: $($processor.NumberOfCores)"
    Write-Host "Logical Processors: $($processor.NumberOfLogicalProcessors)"

    # Get and display memory information
    $totalMemory = [math]::Round($computerSystem.TotalPhysicalMemory / 1GB, 2)
    Write-Host "`nMemory Information:" -ForegroundColor Green
    Write-Host "Total Physical Memory: $totalMemory GB"

    # Get and display disk information
    Write-Host "`nDisk Information:" -ForegroundColor Green
    Get-CimInstance CIM_LogicalDisk | Where-Object {$_.DriveType -eq 3} | ForEach-Object {
        $freeSpace = [math]::Round($_.FreeSpace / 1GB, 2)
        $totalSize = [math]::Round($_.Size / 1GB, 2)
        Write-Host "Drive $($_.DeviceID): $freeSpace GB free of $totalSize GB"
    }

    # Get and display current user information
    $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    Write-Host "`nCurrent User Information:" -ForegroundColor Green
    Write-Host "Username: $($currentUser.Name)"
}

# Main program
Get-SystemInfo

# Pause to keep the console window open
Read-Host "`nPress Enter to exit..."

This script includes:

  1. A function Get-SystemInfo that gathers and displays various system information
  2. Use of PowerShell cmdlets like Get-CimInstance to retrieve system data
  3. Formatting techniques to make the output more readable (colors, alignment)
  4. Basic math operations to convert bytes to gigabytes
  5. Use of pipeline and Where-Object for filtering disk information
  6. Retrieval of current user information

The script provides a comprehensive overview of the system’s hardware and software configuration, making it useful for beginners to understand how to access and present system information using PowerShell. It also introduces concepts like working with CIM instances, formatting output, and basic data manipulation.

Dr. Scripto’s Adventures: The Mysterious Add-Computer Cmdlet

Dr. Scripto, the famous PowerShell researcher, received a strange call one day. A system administrator from a remote company desperately asked for his help:

“Doctor, please help! I need to add a hundred new computers to the domain, but traditional methods are too slow!”

Dr. Scripto smiled. “Don’t worry, my friend! I have a great cmdlet for that!”

He grabbed his magical PowerShell wand and set off on an adventurous journey. Along the way, he met his old friend, the Add-Computer cmdlet.

“Hello, Add-Computer! Ready for some action?” Dr. Scripto asked.

“Always ready, doc! What do you need?” Add-Computer replied enthusiastically.

They arrived together at the company, where the system administrator was impatiently waiting for them. Dr. Scripto introduced his new friend:

“Here’s Add-Computer! He’ll help us add the machines to the domain in a flash!”

The administrator looked at them skeptically. “Are you sure this little cmdlet can do that?”

Dr. Scripto winked and started writing the script:

$computers = Get-Content "C:\ComputerList.txt"
foreach ($computer in $computers) {
    Add-Computer -ComputerName $computer -DomainName "contoso.com" -Credential $cred -Restart
}

Add-Computer lit up and executed the command in an instant. The administrator was amazed to see all the computers join the domain.

“Incredible! You’re real wizards!” he exclaimed happily.

Dr. Scripto and Add-Computer looked at each other and laughed. “It’s not magic, it’s just the power of PowerShell!” they said in unison.

And so, Dr. Scripto and his new friend, Add-Computer, solved the mysterious domain-joining case, proving that the power within PowerShell is truly capable of wonders!

Dr. Scripto

Once upon a time, in a distant, magical IT academy, the most talented system administrators and script masters were trained. At this academy, there was a special day that every student awaited with a mix of excitement and fear: the PowerShell exam day. The exam was conducted by the legendary, albeit slightly eccentric professor, Dr. Scripto, known for presenting the most surprising challenges to his students.

The academy’s newest generation, including the enthusiastic and humorous Tibor, was preparing for the PowerShell exam. Tibor loved humor and decided to showcase it during the exam. As he entered the exam room and saw Dr. Scripto in his usual, slightly disheveled attire, he knew this day would be special.

Dr. Scripto greeted the students and began the exam with a mysterious smile on his face. The first task was to write a simple script that lists all the files in a folder and displays their names. Tibor decided to add his own humorous twist to the task.

Here’s his script:

# File listing script with a touch of humor

Write-Host "Greetings, curious apprentice!"
Write-Host "Now, let me list the treasures of this folder:"

# Listing the contents of the folder
$files = Get-ChildItem -Path .\ -File
foreach ($file in $files) {
    Write-Host "Here you go, here's a file: $($file.Name) "
}

Write-Host "That's all the treasures in the folder. Have a nice day!"

When Tibor submitted the script, Dr. Scripto smiled and ran it instantly. The messages displayed on the screen made everyone in the room laugh. Dr. Scripto not only appreciated the humorous approach but decided to give Tibor a special task that would require even more creativity.

The next task was to write a script that randomly selects a motivational quote and displays it each time it runs. Naturally, Tibor approached this task with humor as well:

# Script filled with motivational quotes

Write-Host "Welcome back, script masters!"

# Motivational quotes
$quotes = @(
    "Don't be afraid to make mistakes; that's what debugging is for! ",
    "The best script is a tested script.",
    "Remember, PowerShell can be your best friend or your worst enemy. ",
    "A good sysadmin knows when to run a script. "
)

# Selecting a random quote
$randomQuote = Get-Random -InputObject $quotes
Write-Host "Motivational message for your day: $randomQuote"

Write-Host "Happy scripting!"

Tibor’s script was once again a huge success, and at the end of the exam, Dr. Scripto declared that not only had Tibor passed the exam, but he also awarded him a golden PowerShell wand for his exceptional creativity and humor.

Thus, Tibor not only passed the PowerShell exam but became a legend at the IT academy, and everyone remembered the day when humor and programming met in the exam room.

Streamlining Your Move to the Cloud: PowerShell Script for Mailbox Migration to Microsoft 365

As organizations transition to cloud-based solutions, migrating mailboxes to Microsoft 365 (formerly Office 365) is a common task. While the process can be complex, PowerShell provides powerful tools to automate and simplify this migration. In this post, we’ll explore a script that helps you migrate on-premises Exchange mailboxes to Microsoft 365.

The Problem: Manually migrating multiple mailboxes to Microsoft 365 is time-consuming and prone to errors.

The Solution: A PowerShell script that automates the process of creating migration batches and initiating the migration to Microsoft 365.

Here’s the script:

# Import required modules
Import-Module ExchangeOnlineManagement

# Connect to Exchange Online
Connect-ExchangeOnline

# Define variables
$CSVFile = "C:\Scripts\MailboxesToMigrate.csv"
$OnPremisesCredential = Get-Credential -Message "Enter on-premises Exchange admin credentials"
$TargetDeliveryDomain = "contoso.mail.onmicrosoft.com"  # Replace with your Microsoft 365 domain
$EndpointName = "OnPremEndpoint"  # Name for the migration endpoint

# Import list of mailboxes to migrate
$Mailboxes = Import-Csv $CSVFile

# Create a migration endpoint (if it doesn't exist)
if (!(Get-MigrationEndpoint -Identity $EndpointName -ErrorAction SilentlyContinue)) {
    New-MigrationEndpoint -ExchangeRemote -Name $EndpointName -Autodiscover -EmailAddress $OnPremisesCredential.UserName -Credentials $OnPremisesCredential
}

# Create a migration batch for each department
$Departments = $Mailboxes | Select-Object -ExpandProperty Department -Unique

foreach ($Dept in $Departments) {
    $BatchName = "Migrate-$Dept-$(Get-Date -Format 'yyyyMMdd')"
    $DeptMailboxes = $Mailboxes | Where-Object { $_.Department -eq $Dept }

    $MigrationBatch = New-MigrationBatch -Name $BatchName -SourceEndpoint $EndpointName -TargetDeliveryDomain $TargetDeliveryDomain
    
    foreach ($Mailbox in $DeptMailboxes) {
        $MoveRequest = New-MoveRequest -Identity $Mailbox.EmailAddress -Remote -RemoteHostName $TargetDeliveryDomain -TargetDeliveryDomain $TargetDeliveryDomain -RemoteCredential $OnPremisesCredential -BatchName $BatchName
    }

    # Start the migration batch
    Start-MigrationBatch -Identity $BatchName
    
    Write-Host "Migration batch $BatchName created and started for department: $Dept"
}

Write-Host "Migration batches created and started for all departments."

# Disconnect from Exchange Online
Disconnect-ExchangeOnline -Confirm:$false

How it works:

  1. The script connects to Exchange Online using the ExchangeOnlineManagement module.
  2. It reads a list of mailboxes to migrate from a CSV file.
  3. A migration endpoint is created if it doesn’t already exist.
  4. The script creates migration batches for each department.
  5. For each mailbox in a department, it creates a move request.
  6. Each migration batch is then started.

To use this script:

  1. Ensure you have the ExchangeOnlineManagement module installed (Install-Module ExchangeOnlineManagement).
  2. Prepare a CSV file (MailboxesToMigrate.csv) with columns: EmailAddress, Department.
  3. Modify the $CSVFile variable to point to your CSV file.
  4. Update the $TargetDeliveryDomain variable with your Microsoft 365 domain.
  5. Run the script in PowerShell with appropriate permissions.

Example CSV content:

CopyEmailAddress,Department
john.doe@contoso.com,Sales
jane.smith@contoso.com,Marketing
mike.johnson@contoso.com,IT
sarah.brown@contoso.com,Sales

Important considerations:

  1. Permissions: Ensure you have the necessary permissions both in your on-premises Exchange environment and in Microsoft 365 to perform migrations.
  2. Network bandwidth: Large-scale migrations can consume significant bandwidth. Plan your migration during off-peak hours if possible.
  3. Testing: Always test the migration process with a small batch of mailboxes before proceeding with a full-scale migration.
  4. User communication: Inform users about the migration process, potential downtime, and any actions they need to take.
  5. Verification: After migration, verify that all mailboxes have been moved successfully and that users can access their data.
  6. Cleanup: Once the migration is complete and verified, you may need to decommission the on-premises mailboxes and update DNS records.

Customizing the script:

  • You can modify the script to include additional parameters in the New-MoveRequest cmdlet, such as BadItemLimit or LargeItemLimit, to handle problematic items during migration.
  • Add error handling and logging to capture any issues that occur during the migration process.
  • Implement a progress bar or more detailed status updates for larger migrations.

Post-migration steps: After running this script and completing the migration, you should:

  1. Monitor the migration batches using the Get-MigrationBatch cmdlet.
  2. Check for any errors or warnings in the migration logs.
  3. Verify that all expected content has been migrated for a sample of users.
  4. Update user guides or documentation to reflect the new Microsoft 365 environment.
  5. Consider implementing additional Microsoft 365 features now that mailboxes are in the cloud.

Migrating mailboxes to Microsoft 365 can be a complex process, but PowerShell scripting can significantly streamline the operation. This script provides a solid foundation for automating your migration, allowing you to move mailboxes efficiently and in an organized manner.

Remember that while this script automates much of the process, it’s crucial to thoroughly plan your migration, prepare your environment, and test thoroughly before executing a large-scale move. Each organization’s needs may vary, so don’t hesitate to adapt this script to your specific requirements.

By leveraging PowerShell for your Microsoft 365 migration, you can ensure a more controlled, efficient, and error-free transition to the cloud. Happy migrating!

Funny Updater

Once upon a time, in a magical IT kingdom, there lived a talented but somewhat forgetful system administrator named Geza. Geza spent his days tending to the servers and keeping the network in order. One fine day, as the sunlight streamed through his window, Geza noticed that one of the servers, which he had named “Howling Wind,” hadn’t updated itself for quite some time. This could be a serious problem, as the system’s security might be at risk.

Geza, the brave sysadmin, decided to write a PowerShell script that would automatically update the server and notify him if any issues arose. Being a fan of humor and witty solutions, he decided to add a little twist to the script. He named the script “Funny Updater.”

Here’s the script:

# Funny Updater Script

# A little greeting to start Geza's day right
Write-Host "Hello, Geza! Get ready for an adventure!"

# Display the current date and time
$currentDate = Get-Date
Write-Host "Today's date is: $currentDate"

# Updating the server
Write-Host "Starting the update..."
try {
    # Update command (just an example)
    Invoke-Expression -Command "Update-Server"
    Write-Host "The server has been successfully updated!"
} catch {
    Write-Host "Oops, something went wrong!"
    Write-Host "Error details: $_"
}

# Display a random joke
$jokes = @(
    "Why can't encryption dance? Because it's always hidden!",
    "What did the router say to the server? Turn off the firewall, let me through! ",
    "What was the fastest ping command called? FlashPing!"
)

# Select a random joke
$randomJoke = Get-Random -InputObject $jokes
Write-Host "Here's a joke to brighten your day: $randomJoke"

# Closing message
Write-Host "All done! Geza, you are the best sysadmin in the world!"

Geza proudly ran the script, and every day, when he updated the server, he was greeted with a new joke. This not only lifted his spirits but also helped him in his work. The kingdom’s servers were safe, and legends sung Geza’s name.

And if they haven’t died yet, they are still laughing at the funny updates to this day.