Skip to content

Commit 91258b6

Browse files
committed
Installer/finalizer in c#
1 parent 8089e68 commit 91258b6

File tree

15 files changed

+225
-780
lines changed

15 files changed

+225
-780
lines changed

Directory.Build.props

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@
99
<Architecture Condition="'$(Architecture)' == '' AND '$(BuildArchitecture)' == 'ppc64le'">$(BuildArchitecture)</Architecture>
1010
<Architecture Condition="'$(Architecture)' == '' AND '$(BuildArchitecture)' == 'loongarch64'">$(BuildArchitecture)</Architecture>
1111
<Architecture Condition="'$(Architecture)' == ''">x64</Architecture>
12-
13-
<!--
14-
The finalizer.nativeproject needs to have Platform set to build for the correct architecture, but this value being set below isn't used by the project because of the build hierarchy when using the CMake SDK.
15-
Instead, the Platform being set here is prior to calling the Arcade SDK, which sets PlatformName in RepoDefaults.props and modifies the OutputPath in ProjectLayout.props to include a PlatformName folder in the path.
16-
Note: The redist-installer project does use Architecture. The Arcade SDK does not use either BuildArchitecture or Architecture.
17-
-->
18-
<Platform Condition="'$(SetPlatformFromArchitecture)' == 'true' And ('$(Platform)' == '' Or '$(Platform)' == 'AnyCPU')">$(Architecture)</Platform>
1912
</PropertyGroup>
2013

2114
<Import Project="Sdk.props" Sdk="Microsoft.DotNet.Arcade.Sdk" />

NuGet.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
<add key="vs-impl" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-impl/nuget/v3/index.json" />
2929
<!-- Used for Rich Navigation indexing task -->
3030
<add key="richnav" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-buildservices/nuget/v3/index.json" />
31+
32+
<!-- remove once https://www.nuget.org/packages/WixToolset.Dtf.WindowsInstaller is uploaded to dotnet feed -->
33+
<add key="local" value="c:\temp\packages" />
3134
</packageSources>
3235
<disabledPackageSources>
3336
<clear />

eng/dependabot/Packages.props

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
<!-- Packages in this file have versions updated periodically by Dependabot. Versions managed by Darc/Maestro should be in ..\Packages.props. -->
44

55
<ItemGroup>
6+
<PackageVersion Include="WixToolset.Dtf.WindowsInstaller" Version="5.0.2" />
7+
68
<!--Test dependencies-->
79
<PackageVersion Include="Verify.Xunit" Version="25.0.2" />
810
<PackageVersion Include="Verify.DiffPlex" Version="3.0.0" />

global.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,9 @@
1313
"version": "16.8"
1414
}
1515
},
16-
"native-tools": {
17-
"cmake": "latest"
18-
},
1916
"msbuild-sdks": {
2017
"Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.24531.2",
2118
"Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.24531.2",
22-
"Microsoft.Build.NoTargets": "3.7.0",
23-
"Microsoft.DotNet.CMake.Sdk": "9.0.0-beta.24217.1"
19+
"Microsoft.Build.NoTargets": "3.7.0"
2420
}
2521
}

sdk.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installer", "Installer", "{
495495
EndProject
496496
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "core-sdk-tasks", "src\Installer\core-sdk-tasks\core-sdk-tasks.csproj", "{5593C59B-442B-4783-8527-74F6E41668D9}"
497497
EndProject
498-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "finalizer-build", "src\Installer\finalizer\finalizer-build.csproj", "{32DA04FF-A951-43EA-B2FA-86A825009A97}"
498+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "finalizer", "src\Installer\finalizer\finalizer.csproj", "{32DA04FF-A951-43EA-B2FA-86A825009A97}"
499499
EndProject
500500
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "redist-installer", "src\Installer\redist-installer\redist-installer.csproj", "{FAADC193-BA41-449D-97CE-0EF82836046A}"
501501
EndProject

src/Installer/finalizer/CMakeLists.txt

Lines changed: 0 additions & 65 deletions
This file was deleted.
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
<Project>
22

3-
<PropertyGroup>
4-
<SetPlatformFromArchitecture>true</SetPlatformFromArchitecture>
5-
</PropertyGroup>
6-
73
<Import Project="..\..\..\Directory.Build.props" />
84

9-
</Project>
5+
</Project>

src/Installer/finalizer/Program.cs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using Microsoft.Win32;
7+
using WixToolset.Dtf.WindowsInstaller;
8+
9+
if (args.Length < 3)
10+
{
11+
Console.WriteLine("Invalid arguments. Usage: DotNetSdkManager <logPath> <sdkVersion> <platform>");
12+
return;
13+
}
14+
15+
string logPath = args[0];
16+
string sdkVersion = args[1];
17+
string platform = args[2];
18+
19+
Console.WriteLine($"{nameof(logPath)}: {logPath}");
20+
Console.WriteLine($"{nameof(sdkVersion)}: {sdkVersion}");
21+
Console.WriteLine($"{nameof(platform)}: {platform}");
22+
23+
try
24+
{
25+
// Step 1: Parse and format SDK feature band version
26+
string featureBandVersion = ParseSdkVersion(sdkVersion);
27+
28+
// Step 2: Check if SDK feature band is installed
29+
bool isInstalled = DetectSdk(featureBandVersion, platform);
30+
if (isInstalled)
31+
{
32+
Console.WriteLine($"SDK with feature band {featureBandVersion} is already installed.");
33+
return;
34+
}
35+
36+
// Step 3: Remove dependent components if necessary
37+
bool restartRequired = RemoveDependent(featureBandVersion, platform);
38+
if (restartRequired)
39+
{
40+
Console.WriteLine("A restart may be required after removing the dependent component.");
41+
}
42+
43+
// Step 4: Delete workload records
44+
DeleteWorkloadRecords(featureBandVersion, platform);
45+
46+
// Step 5: Clean up install state file
47+
RemoveInstallStateFile(featureBandVersion, platform);
48+
49+
// Final reboot check
50+
if (restartRequired || IsRebootPending())
51+
{
52+
Console.WriteLine("A system restart is recommended to complete the operation.");
53+
}
54+
else
55+
{
56+
Console.WriteLine("Operation completed successfully. No restart is required.");
57+
}
58+
}
59+
catch (Exception ex)
60+
{
61+
Console.WriteLine($"Error: {ex.Message}");
62+
}
63+
64+
static string ParseSdkVersion(string sdkVersion)
65+
{
66+
var parts = sdkVersion.Split('.');
67+
if (parts.Length < 3)
68+
throw new ArgumentException("Invalid SDK version format.");
69+
70+
if (!int.TryParse(parts[2], out int patch) || patch < 100)
71+
throw new ArgumentException("Invalid patch level in SDK version.");
72+
73+
int featureBand = patch - (patch % 100);
74+
return $"{parts[0]}.{parts[1]}.{featureBand}";
75+
}
76+
77+
static bool DetectSdk(string featureBandVersion, string platform)
78+
{
79+
string registryPath = $@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{platform}\sdk";
80+
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath))
81+
{
82+
if (key == null)
83+
{
84+
Console.WriteLine("SDK registry path not found.");
85+
return false;
86+
}
87+
88+
foreach (var valueName in key.GetValueNames())
89+
{
90+
if (valueName.Contains(featureBandVersion))
91+
{
92+
Console.WriteLine($"SDK version detected: {valueName}");
93+
return true;
94+
}
95+
}
96+
}
97+
return false;
98+
}
99+
100+
static bool RemoveDependent(string featureBandVersion, string platform)
101+
{
102+
bool restartRequired = false;
103+
104+
string dependentName = $"Microsoft.NET.Sdk,{featureBandVersion},{platform}";
105+
string productCode = LookupProductCode(dependentName);
106+
107+
if (string.IsNullOrEmpty(productCode))
108+
{
109+
Console.WriteLine($"No product code found for dependent: {dependentName}");
110+
return restartRequired;
111+
}
112+
113+
Installer.SetInternalUI(InstallUIOptions.Silent);
114+
Installer.ConfigureProduct(productCode, 0, InstallState.Absent, "");
115+
116+
Console.WriteLine($"Removed dependent: {dependentName}");
117+
restartRequired = true; // Assume restart may be needed after uninstalling a major SDK component
118+
119+
return restartRequired;
120+
}
121+
122+
static string LookupProductCode(string dependentName)
123+
{
124+
foreach (var product in ProductInstallation.AllProducts)
125+
{
126+
if (product.ProductName.Contains(dependentName))
127+
{
128+
return product.ProductCode;
129+
}
130+
}
131+
return null;
132+
}
133+
134+
static void DeleteWorkloadRecords(string featureBandVersion, string platform)
135+
{
136+
string workloadKey = $@"SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\{platform}";
137+
138+
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(workloadKey, writable: true))
139+
{
140+
if (key != null)
141+
{
142+
key.DeleteSubKeyTree(featureBandVersion, throwOnMissingSubKey: false);
143+
Console.WriteLine($"Deleted workload records for '{featureBandVersion}'.");
144+
}
145+
else
146+
{
147+
Console.WriteLine("No workload records found to delete.");
148+
}
149+
}
150+
}
151+
152+
static void RemoveInstallStateFile(string featureBandVersion, string platform)
153+
{
154+
string programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
155+
string installStatePath = Path.Combine(programDataPath, "dotnet", "workloads", platform, featureBandVersion, "installstate", "default.json");
156+
157+
if (File.Exists(installStatePath))
158+
{
159+
File.Delete(installStatePath);
160+
Console.WriteLine($"Deleted install state file: {installStatePath}");
161+
162+
var dir = new DirectoryInfo(installStatePath).Parent;
163+
while (dir != null && dir.Exists && dir.GetFiles().Length == 0 && dir.GetDirectories().Length == 0)
164+
{
165+
dir.Delete();
166+
dir = dir.Parent;
167+
}
168+
}
169+
else
170+
{
171+
Console.WriteLine("Install state file does not exist.");
172+
}
173+
}
174+
175+
static bool IsRebootPending()
176+
{
177+
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending"))
178+
{
179+
if (key != null)
180+
{
181+
Console.WriteLine("Reboot is pending due to component-based servicing.");
182+
return true;
183+
}
184+
}
185+
186+
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\Session Manager"))
187+
{
188+
var value = key?.GetValue("PendingFileRenameOperations");
189+
if (value != null)
190+
{
191+
Console.WriteLine("Pending file rename operations indicate a reboot is pending.");
192+
return true;
193+
}
194+
}
195+
196+
Console.WriteLine("No reboot pending.");
197+
return false;
198+
}

src/Installer/finalizer/finalizer-build.csproj

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)