Post

Powershell Challenge dotnetSDK

Downloads the .NET SDK, check the downloaded file's signature and issuer are valid. Check with VirusTotal API using the hash value of the downloaded file. Install the SDK silently, log all installed SDK versions, and then finally uninstall all the previous versions.

Powershell Challenge

Had some trouble with the last part of this script near the end of my 2 hour time constraint - uninstalling all previous versions. Sometimes I overthink a problem, and create a complex function when a simple approach would have been better and actually work. Otherwise, this script is complete and works as intended. And it was a lot of fun doing it!

What this script essentially does is download the .NET SDK, check the downloaded file’s signature and issuer are valid. Check with VirusTotal API using the hash value of the downloaded file. Install the SDK silently, log all installed SDK versions, and then finally uninstall all the previous versions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<#
.NOTES

Name: DotnetSDK.ps1
Author: B. Werkman
Version: 1.1
DateCreated: 19 Jan 24
#>

# Download .NET SDK
$installDir = "C:\install"
$downloadUrl = "https://download.visualstudio.microsoft.com/download/pr/cb56b18a-e2a6-4f24-be1d-fc4f023c9cc8/be3822e20b990cf180bb94ea8fbc42fe/dotnet-sdk-8.0.101-win-x64.exe"
$filePath = Join-Path -Path $installDir -ChildPath "dotnet-sdk-8.0.101-win-x64.exe"

# Define log file path with current date, time, and hostname
$currentDateTime = Get-Date -Format "yyyyMMddHHmmss"
$hostName = $env:COMPUTERNAME
$logPath = "C:\install\$($currentDateTime)_$($hostName).log"

# Create directory if it doesn't exist
if (!(Test-Path -Path $installDir)) {
    New-Item -ItemType Directory -Path $installDir
}

# Function to write log
function Write-Log {
    param (
        [Parameter(ValueFromPipeline=$true)]
        [string]$message
    )
    Process {
        "$((Get-Date).ToString()) : $message" | Out-File -FilePath $logPath -Append
    }
}

# Download file
Invoke-WebRequest -Uri $downloadUrl -OutFile $filePath

# Validate Authenticode signature and log if invalid
$signature = Get-AuthenticodeSignature -FilePath $filePath

$expectedIssuer = "CN=Microsoft Code Signing PCA 2011, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"

# Check the signature status and issuer
if ($signature.Status -ne "Valid" -or $signature.SignerCertificate.Issuer -ne $expectedIssuer) {
    $logContent = "Invalid signature or issuer.`n"
    $logContent += "File Path: $filePath`n"
    $logContent += "Signature Status: $($signature.Status)`n"
    $logContent += "Issuer: $($signature.SignerCertificate.Issuer)`n"
    $logContent | Write-Log
    exit
}

# Check with VirusTotal
$apiKey = "3f50cb994f2859e6a81a14cc751df6aab11456e08405afb393f035f62456a373"
$fileHash = (Get-FileHash -Path $filePath -Algorithm SHA256).Hash
$virusTotalUrl = "https://www.virustotal.com/api/v3/files/$fileHash"

$headers = @{
    "x-apikey" = $apiKey
}

try {
$response = Invoke-RestMethod -Uri $virusTotalUrl -Method Get -Headers $headers -ErrorAction Stop
} catch {
    "Failed to get a response from VirusTotal. Error: $_" | Write-Log
    exit
}

# Check for bad reputation and log details

if ($response.data.attributes.reputation -lt 0) {
    $logContent = "File has a bad reputation on VirusTotal.`n"
    $logContent += "File Path: $filePath`n"
    $logContent += "VirusTotal Response: $($response | ConvertTo-Json -Depth 5)`n"
    $logContent | Write-Log
    exit
}

# Install .NET SDK
Start-Process -FilePath $filePath -ArgumentList "/quiet /norestart" -Wait

# Validate Installation
$dotNetPath = "${env:ProgramFiles}\dotnet\sdk\8.0.101" # Path where .NET SDK is expected to be installed

$installationValid = $false
if (Test-Path -Path $dotNetPath) {
    $installedVersions = Get-ChildItem -Path "${env:ProgramFiles}\dotnet\sdk\" | Select-Object -ExpandProperty Name
    if ("8.0.101" -in $installedVersions) {
        $installationValid = $true
    }
}

if (-not $installationValid) {
    "Installation of .NET SDK 8.0.101 failed or was not verified." | Write-Log
    exit
}

# Enumerate and Uninstall Previous Versions of .NET SDK
$targetVersion = "8.0.101"

$dotNetSdks = Get-ChildItem -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
    Get-ItemProperty |
    Where-Object { $_.DisplayName -like "*.NET SDK*" -or $_.DisplayName -like "*.NET Core SDK*" }
    foreach ($sdk in $dotNetSdks) {
        Write-Log "Found SDK: $($sdk.DisplayName)"

       # Check if the DisplayName contains the target version
        if ($sdk.DisplayName -notlike "*$targetVersion*") {
            if ($sdk.UninstallString) {
                Write-Log "Uninstalling $($sdk.DisplayName)"
                Start-Process -FilePath "cmd.exe" -ArgumentList "/c", $($sdk.UninstallString + " /quiet /norestart") -Wait
                Write-Log "$($sdk.DisplayName) uninstalled"
            }
            else {
                Write-Log "No uninstall string found for $($sdk.DisplayName)"
            }
        }
        else {
            Write-Log "Skipping $($sdk.DisplayName), as it is the target version or version parsing failed"
        }
    }
This post is licensed under CC BY 4.0 by the author.