Morning all I am trying to uninstall Microsoft 365 products using VB.NET with .NET Frameworks 9.0.
I have a list of M365 products that need to be unisntalled saved as a array
Dim officeProductIds As String() = {
"O365ProPlusEEANoTeamsRetail",
"O365ProPlusRetail",
"O365BusinessEEANoTeamsRetail",
"O365BusinessRetail"
}
I then find the uninstall string by looking in the following registry keys
Dim uninstallPaths As String() = {
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
}
I then ass this down and call PowerShell to uninstall the application with the following code
Try
Dim baseKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)
For Each regPath In uninstallPaths
Using uninstallKey As RegistryKey = baseKey.OpenSubKey(regPath)
If uninstallKey Is Nothing Then Continue For
For Each subKeyName As String In uninstallKey.GetSubKeyNames()
If officeProductIds.Any(Function(id) subKeyName.Contains(id)) Then
Dim uninstallString As String = ReadRegistry.GetOfficeUninstallString(subKeyName)
If Not String.IsNullOrEmpty(uninstallString) Then
Console.WriteLine($"[FOUND] Uninstall string for {subKeyName}: {uninstallString}")
Dim installerPath As String = ""
Dim arguments As String = ""
If uninstallString.StartsWith(ChrW(34)) Then
Dim endQuoteIndex As Integer = uninstallString.IndexOf(ChrW(34), 1)
installerPath = uninstallString.Substring(1, endQuoteIndex - 1)
arguments = uninstallString.Substring(endQuoteIndex + 1).Trim()
Else
Dim firstSpace = uninstallString.IndexOf(" "c)
If firstSpace > 0 Then
installerPath = uninstallString.Substring(0, firstSpace)
arguments = uninstallString.Substring(firstSpace + 1).Trim()
Else
installerPath = uninstallString
End If
End If
Dim installerFolder As String = IO.Path.GetDirectoryName(installerPath)
Console.WriteLine()
Console.WriteLine($"Installer: {IO.Path.GetFileName(installerPath)}")
Console.WriteLine($"Install Folder: {installerFolder}")
Console.WriteLine($"Arguments: {arguments}")
Console.WriteLine()
ApplicationInstallerHelper.UninstallApplication(IO.Path.GetFileName(installerPath), installerFolder, True, arguments)
Else
Console.WriteLine($"[WARNING] No UninstallString for {subKeyName}")
End If
End If
Next
End Using
Next
Catch ex As Exception
Console.WriteLine("[EXCEPTION] " & ex.Message)
End Try
It then shows it as completed but when I look in Windows Installed Applications 365 products are still listed.
Just FWI the code below
ApplicationInstallerHelper.UninstallApplication(IO.Path.GetFileName(installerPath), installerFolder, True, arguments)
Public Sub UninstallApplication(installerFileName As String, installerFolderPath As String, Optional silent As Boolean = False, Optional installerArguments As String = "")
Dim fullPath As String = IO.Path.Combine(installerFolderPath, installerFileName)
If Not IO.File.Exists(fullPath) Then
StrBuilder.WrapMessage($"Installer not found at: {fullPath}", "ERR")
Return
End If
If silent AndAlso Not installerArguments.Contains("/quiet") Then
installerArguments &= " /quiet"
End If
If silent AndAlso Not installerArguments.Contains("/norestart") Then
installerArguments &= " /norestart"
End If
Dim psCommand As String =
$"Start-Process -FilePath '{fullPath}' -ArgumentList '{installerArguments}' -Wait -NoNewWindow; exit $LASTEXITCODE"
Dim psi As New ProcessStartInfo("powershell.exe", $"-NoProfile -ExecutionPolicy Bypass -Command ""{psCommand}""")
psi.UseShellExecute = False
psi.RedirectStandardOutput = True
psi.RedirectStandardError = True
psi.CreateNoWindow = True
Dim proc As Process = Process.Start(psi)
proc.WaitForExit()
Dim output As String = proc.StandardOutput.ReadToEnd()
Dim errors As String = proc.StandardError.ReadToEnd()
Dim code As Integer = proc.ExitCode
If code = 0 Then
StrBuilder.WrapMessage("Uninstall completed successfully.", "OK")
Else
StrBuilder.WrapMessage($"Uninstall failed with exit code {code}", "ERR")
If Not String.IsNullOrWhiteSpace(errors) Then
Console.WriteLine("[STDERR] " & errors)
End If
End If
End Sub
Just translate the string into something my code will understand and excite via PowerShell.
I have been at this for a month so any support would be welcome
You'll need to run your app with administrative privileges. It's not necessary to use PowerShell with System.Diagnostics.Process. Also, some of the arguments you've specified are for msiexec not for Click-To-Run. Try the following:
Add Application Manifest File:
Open Solution Explorer:
Modify requestedExecutionLevel:
Change From:
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
Change To:
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
Add a class to your project (name: UninstallInfo.vb)
Public Class UninstallInfo
Public Property Arguments As String = String.Empty
Public Property Filename As String = String.Empty
Public Property InstallFolder As String = String.Empty
Public Property Installer As String = String.Empty
End Class
Add a module to your project (name: Module1.vb)
Imports System.Text
Imports Microsoft.Win32
Module Module1
Private officeProductIds As String() = {
"O365ProPlusEEANoTeamsRetail",
"O365ProPlusRetail",
"O365BusinessEEANoTeamsRetail",
"O365BusinessRetail"
}
Private Function GetOffice365UninstallInfo() As List(Of UninstallInfo)
Dim uninstallInfos As List(Of UninstallInfo) = New List(Of UninstallInfo)()
Dim uninstallPaths As String() = {
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
}
Try
Dim baseKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)
For Each regPath In uninstallPaths
Using uninstallKey As RegistryKey = baseKey.OpenSubKey(regPath)
If uninstallKey Is Nothing Then Continue For
For Each subKeyName As String In uninstallKey.GetSubKeyNames()
'Debug.WriteLine($"subkeyName: {subKeyName}")
If officeProductIds.Any(Function(id) subKeyName.Contains(id)) Then
Using skey As RegistryKey = uninstallKey.OpenSubKey(subKeyName, False)
If skey IsNot Nothing Then
Dim uninstallString As String = skey.GetValue("UninstallString", String.Empty)?.ToString()
If Not String.IsNullOrEmpty(uninstallString) Then
Debug.WriteLine($"[FOUND] Uninstall string for {subKeyName}: {uninstallString}")
Dim installerPath As String = ""
Dim arguments As String = ""
If uninstallString.StartsWith(ChrW(34)) Then
Dim endQuoteIndex As Integer = uninstallString.IndexOf(ChrW(34), 1)
installerPath = uninstallString.Substring(1, endQuoteIndex - 1)
arguments = uninstallString.Substring(endQuoteIndex + 1).Trim()
Else
Dim firstSpace = uninstallString.IndexOf(" "c)
If firstSpace > 0 Then
installerPath = uninstallString.Substring(0, firstSpace)
arguments = uninstallString.Substring(firstSpace + 1).Trim()
Else
installerPath = uninstallString
End If
End If
Dim installerFolder As String = IO.Path.GetDirectoryName(installerPath)
Debug.WriteLine("")
Debug.WriteLine($"Installer: {System.IO.Path.GetFileName(installerPath)}")
Debug.WriteLine($"Install Folder: {installerFolder}")
Debug.WriteLine($"Arguments: {arguments}")
Debug.WriteLine("")
'add
uninstallInfos.Add(New UninstallInfo() With {.Arguments = arguments, .Filename = installerPath, .Installer = System.IO.Path.GetFileName(installerPath), .InstallFolder = installerFolder})
Else
Debug.WriteLine($"[WARNING] No UninstallString for {subKeyName}")
End If
End If
End Using
End If
Next
End Using
Next
Catch ex As Exception
Debug.WriteLine("[EXCEPTION] " & ex.Message)
End Try
Return uninstallInfos
End Function
Public Function UninstallOffice365() As List(Of String)
Dim results As List(Of String) = New List(Of String)()
'get Office 365 uinstall info
Dim installations As List(Of UninstallInfo) = GetOffice365UninstallInfo()
If installations.Count = 0 Then
'add
results.Add("Info: No matching Office 365 installations found.")
Else
For Each installation In installations
Dim result As String = RunProcess(installation.Filename, installation.Arguments, installation.InstallFolder, True)
Debug.WriteLine($"result: {Environment.NewLine}{result}{Environment.NewLine}")
'add
results.Add(result)
Next
End If
Return results
End Function
Private Function RunProcess(filename As String, arguments As String, workingDirectory As String, Optional silent As Boolean = False) As String
'create new instance and set properties
Dim startInfo As ProcessStartInfo = New ProcessStartInfo(filename) With {.CreateNoWindow = True, .RedirectStandardError = True, .RedirectStandardOutput = True, .UseShellExecute = False, .Verb = "runas", .WindowStyle = ProcessWindowStyle.Hidden, .WorkingDirectory = workingDirectory}
If silent AndAlso Not arguments.Contains("DisplayLevel=False") Then
arguments &= " DisplayLevel=False"
End If
Debug.WriteLine($"arguments: {arguments}")
If Not String.IsNullOrEmpty(arguments) Then
startInfo.Arguments = arguments
End If
Dim outputSB As StringBuilder = New StringBuilder()
Using p As Process = New Process() With {.EnableRaisingEvents = True, .StartInfo = startInfo}
AddHandler p.ErrorDataReceived, Sub(sender As Object, e As DataReceivedEventArgs)
If Not String.IsNullOrWhiteSpace(e.Data) Then
'ToDo: add desired code
Debug.WriteLine("error: " & e.Data)
outputSB.AppendFormat($"Error: {e.Data}{Environment.NewLine}")
End If
End Sub
AddHandler p.OutputDataReceived, Sub(sender As Object, e As DataReceivedEventArgs)
If Not String.IsNullOrWhiteSpace(e.Data) Then
'ToDo: add desired code
Debug.WriteLine("output: " & e.Data)
outputSB.AppendFormat($"{e.Data}{Environment.NewLine}")
End If
End Sub
p.Start()
p.BeginErrorReadLine()
p.BeginOutputReadLine()
'wait for exit
p.WaitForExit()
End Using
Return outputSB.ToString()
End Function
End Module
Note: While it's good practice to capture StandardError and StandardOutput, since the uninstaller is graphical, and DisplayLevel=False
is specified it's unlikely there will be any StandardError or StandardOutput data. You may consider executing GetOffice365UninstallInfo() again after the uinstall to ensure that the Office 365 versions that you specified to uninstall have been uninstalled.
Usage:
Dim results As List(Of String) = UninstallOffice365()
For Each result In results
Debug.WriteLine($"{result}{Environment.NewLine}")
Next
Also see Script To Silently Uninstall Built-In Office 365 ClickToRun and this post.