OSD Data Collection and Teams Webhook Integration

###################################################################

UPDATE: 10.6.2024

Some customers want maybe know the link speed for download, but even for upload. The script has been extended with these information:

try {
#region Scrape the webpage to get the download link
function Get-SpeedTestDownloadLink {
    $url = "https://www.speedtest.net/apps/cli"
    $webContent = Invoke-WebRequest -Uri $url -UseBasicParsing
    if ($webContent.Content -match 'href="(https://install\.speedtest\.net/app/cli/ookla-speedtest-[\d\.]+-win64\.zip)"') {
        return $matches[1]
    } else {
        Write-Output "Unable to find the win64 zip download link."
        return $null
    }
}
#endregion

#region Download and extract the zip file
function Download-SpeedTestZip {
    param (
        [string]$downloadLink,
        [string]$destination
    )
    Invoke-WebRequest -Uri $downloadLink -OutFile $destination -UseBasicParsing
}

function Extract-Zip {
    param (
        [string]$zipPath,
        [string]$destination
    )
    Add-Type -AssemblyName System.IO.Compression.FileSystem
    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath, $destination)
}
#endregion

#region Run the speedtest executable
function Run-SpeedTest {
    param (
        [string]$executablePath,
        [array]$arguments
    )

    # Check if '--accept-license' is already in arguments
    if (-not ($arguments -contains "--accept-license")) {
        $arguments += "--accept-license"
    }

    # Check if '--accept-gdpr' is already in arguments
    if (-not ($arguments -contains "--accept-gdpr")) {
        $arguments += "--accept-gdpr"
    }

    $Result = & $executablePath $arguments

    return $Result
}
#endregion

#region Cleanup
function Remove-File {
    param (
        [string]$Path
    )
    try {
        if (Test-Path -Path $Path) {
            Remove-Item -Path $Path -Recurse -ErrorAction Stop
        }
    } catch {
        Write-Debug "Unable to remove item: $_"
    }
}

function Remove-Files {
    param(
        [string]$zipPath,
        [string]$folderPath
    )
    Remove-File -Path $zipPath
    Remove-File -Path $folderPath
}
#endregion

$tempFolder = $env:TEMP
$zipFilePath = Join-Path $tempFolder "speedtest-win64.zip"
$extractFolderPath = Join-Path $tempFolder "speedtest-win64"

Remove-Files -zipPath $zipFilePath -folderPath $extractFolderPath

$downloadLink = Get-SpeedTestDownloadLink
Write-SectionHeader "Downloading SpeedTest CLI"
Download-SpeedTestZip -downloadLink $downloadLink -destination $zipFilePath

Write-SectionHeader "Extracting Zip File"
Extract-Zip -zipPath $zipFilePath -destination $extractFolderPath

$executablePath = Join-Path $extractFolderPath "speedtest.exe"
Write-SectionHeader "Running SpeedTest"
$Result = Run-SpeedTest -executablePath $executablePath -arguments $ScriptArgs

$DownloadSpeed = [regex]::match(($Result | where-object { $_ -like "*Download:*" }).trim(), '[0-9]+\.?[0-9]*').value
$UploadSpeed = [regex]::match(($Result | where-object { $_ -like "*Upload:*" }).trim(), '[0-9]+\.?[0-9]*').value
#$ISP = ($Result | where-object { $_ -like "*ISP:*" }).trim().split(":")[1].trim()
#$server = ($Result | where-object { $_ -like "*Server:*" }).trim().split(":")[1].trim()
$SpeedTestURL = ($Result | where-object { $_ -like "*Result URL:*" }).trim().split(" ")[2].trim()

Write-DarkGrayLine
Write-Host -ForegroundColor Green "Download: $DownloadSpeed Mbps"
Write-Host -ForegroundColor Green "Upload: $UploadSpeed Mbps"
Write-Host -ForegroundColor Green "Result URL: $SpeedTestURL"

Write-SectionHeader "Cleaning up"
Remove-Files -zipPath $zipFilePath -folderPath $extractFolderPath

Write-SectionSuccess "Done"
} catch {
    Write-Error "An error occurred: $_"
}

If you want, you can use the speed test script in a standalone mode as well –> https://speedtest.osdcloud.ch

###################################################################

Regardless of the specific OSD technique or platform you choose – whether it’s OSDCloud, ConfigMgr, or any other solution – one common desire remains: receiving notifications when the deployment process completes.

In my work, I frequently rely on the widely-used OSDCloud PowerShell module. This module provides a powerful set of tools for OSD tasks, including an intriguing function called Get-OSDGather.

Let’s explore how this function can enhance your OSD workflows and keep you informed about deployment progress.

$module = Import-Module OSD -PassThru -ErrorAction Ignore
if (-not $module) {
    Write-Host "Installing OSD module"
    Install-Module OSD -Force | Out-Null
}
Import-Module OSD -Force | Out-Null

$OSDGatheringJSON = Get-OSDGather -Full | ConvertTo-Json

If you are using completely OSDCloud technique, the process creates a very informative JSON file on the system drive, which can be used to grab some timestamp information.

$JsonPath = "C:\OSDCloud\Logs\OSDCloud.json"
if (Test-Path $JsonPath){

    $JSON= Get-Content -Path $JsonPath -Raw | ConvertFrom-Json
    $WinPECompleted = "$($JSON.TimeSpan.Minutes) minutes $($JSON.TimeSpan.Seconds) seconds"

    $OSDEnd = Get-Date
    $OSDCouldTime = New-TimeSpan -Start $JSON.TimeStart.DateTime -End $OSDEnd

    $OSDCouldTimeCompleted = "$($OSDCouldTime.Hours) hour(s) $($OSDCouldTime.Minutes) minutes $($OSDCouldTime.Seconds) seconds"
}

The following values can be an example to receive the computer and OS information.

$ComputerName = $OSDGathering.OperatingSystem.CSName
$ComputerModel = $OSDGathering.ComputerSystemProduct.Name

$OS = $OSDGathering.OperatingSystem.Caption
$OSVersion = $OSDGathering.OperatingSystem.Version

$BiosSerialNumber = $OSDGathering.BIOS.SerialNumber
$BiosVersion = $OSDGathering.BIOS.SMBIOSBIOSVersion
$BiosReleaseDate = $OSDGathering.BIOS.ReleaseDate

$OSDCloudVersion = (Get-Module -Name OSD -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1).Version.ToString()

$IPAddress = (Get-WmiObject win32_Networkadapterconfiguration | Where-Object{ $_.ipaddress -notlike $null }).IPaddress | Select-Object -First 1
$Connection = Get-NetAdapter -physical | Where-Object status -eq 'up'
$ConnectionName = $connection.Name
$ConnectionDescription = $connection.InterfaceDescription
$LinkSpeed = $connection.LinkSpeed
$SSIDset = (Get-NetConnectionProfile).Name

These values can be then send via webhook to a predefined Teams channel:

$URI = 'XXXX'
$JSON = @{
    "@type"    = "MessageCard"
    "@context" = "<http://schema.org/extensions>"
    "title"    = 'OSD Information'
    "text"     = "The following client has been successfully deployed:<br>
                  Computer Name: **$($ComputerName)**<br>
                  Model: **$($ComputerModel)**<br>
                  <br>
                  OS Version: **$($OS) $($OSVersion)**<br>
                  <br>
                  BIOS Serial Number: **$($BiosSerialNumber)**<br>
                  BIOS Version: **$($BiosVersion)**<br>
                  BIOS Release Date: **$($BiosReleaseDate)**<br>
                  <br>
                  Connection Type: **$($ConnectionName)**<br>
                  SSID: **$($SSIDset)**<br>
                  IP Address: **$($IPAddress)**<br>
                  Connection Description: **$($ConnectionDescription)**<br>
                  Link Speed: **$($LinkSpeed)**<br>
                  <br>
                  OSDCloud Version: **$($OSDCloudVersion)**<br>
                  WinPE Time Completed: **$($WinPECompleted)**<br>
                  OSDCloud Time Completed: **$($OSDCouldTimeCompleted)**<br>
                  <br>
                  "
    } | ConvertTo-JSON
    
$Params = @{
"URI"         = $URI
"Method"      = 'POST'
"Body"        = $JSON
"ContentType" = 'application/json'
}
Invoke-RestMethod @Params | Out-Null

This script can be executed in the OOBE phase or if you like, you can pack it into a Win32 app, and use it in the ESP phase.

The end result looks in the Teams channel like that:

Happy data collecting and sorting your relevant OSD information!

Leave a Reply

Your email address will not be published. Required fields are marked *