SCCM HKCU Detection Issue - powershell

I am deploying a package install via SCCM however I have the detection method to run the following powershell script to detect a HKCU key for whoever is currently logged on. SCCM installs the app as 'SYSTEM' account and I have not chosen to 'install for user' as this prompts our users with a UAC dialog box which i am trying to avoid.
Even though it seems like the following code has worked, when i manually alter the registry key to something other than whats specified, Software Center still shows the app as 'installed' even though in theory it shouldnt as the key has been manually amended.
What am i doing wrong here?
$key = 'HKCU:\SOFTWARE\MCF\Kofax'
$valueName = 'Cert'
$valueData = '1.0'
if ((Get-ItemPropertyValue $key $valueName) -eq $valueData) {
"installed"
} else {
"NOT installed"
}

You can"t have an output if the software is NOT found. Every output in a detection via PowerShell counts as "Software detected".
Just delete "NOT Installed" from your script and it should work.

Related

Powershell Script to reinstall Microsoft teams, Not able to find teams install

Currently I am trying to write a power shell script that will uninstall then install Microsoft Teams.
I have never written a power shell script before and I am having trouble having the script get the initial teams installation so I can uninstall it.
This is what I have written so far, I saw two ways of finding the teams install online and neither is able to find it so I am kinda lost, any help would be much appreciated.
(I know both are commented out I just did it like this for formatting in this question.)
Write-Host "-------------------------------------`n"
# Prompt for credentials
$credential = Get-Credential
$username = $credential.Username
$password = $credential.GetNetworkCredential().Password
Write-Host "Finding teams`n"
# Find teams 1
#$teamsapp = Get-AppxPackage -Name Microsoft.Teams
# Find teams 2
#$teamsapp = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -eq "Microsoft Teams" }
# Check if installed
if ($teamsapp) {
Write-Host "Microsoft Teams is installed."
} else {
Write-Host "Microsoft Teams is not installed."
}
`
Teams is a bit tricky because it installs per user, not per computer. Assuming you're running the script under the user's account, you can check the following registry location using Get-ChildItem:
Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Teams
This code worked for me:
Get-ChildItem -Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' | Where-Object { $_.Name -like '*Teams' }
You should be able to use the "QuietUninstallString" property of the result to get the command needed to uninstall Teams.
As a side note, consider looking into the Teams Machine Wide Installer for deploying teams. It installs to the computer and runs at logon for each user to detect if Teams is installed to their AppData folder. If not, it installs it automatically. This lets you avoid having to run as the user or loop through all the users AppData folder to manipulate user apps.

Automate Microsoft Teams Tab App in AzurePipelines

We are trying to automate a Teams Tab App in our Azure Pipeline, but we are wondering if this even possible. We have already created the zip file for the app which can be uploaded via App Studio and it works. But we don't want the customers to do that via App Studio, instead, we want to automate this process on their pipeline. For that we created the following powershell:
# Generate zip file for deployment
$compress = #{
Path = "color.png", "outline.png", "manifest.json"
CompressionLevel = "Fastest"
DestinationPath = "app.zip"
}
Compress-Archive #compress -Update
Then we check if MicrosoftTeams module is installed, otherwise we install it:
# Checks whether MicrosoftTeams module is available
if (Get-Module -ListAvailable -Name "MicrosoftTeams") {
Write-Verbose "MicrosoftTeams module already installed."
}
else {
Write-Verbose "Installing module MicrosoftTeams - https://learn.microsoft.com/en-us/powershell/module/teams/?view=teams-ps."
Install-Module MicrosoftTeams
}
Write-Verbose "Importing module MicrosoftTeams."
Import-Module MicrosoftTeams
And we connect with Microsoft Teams so we can install the app later:
Write-Verbose "Connecting to Microsoft Teams"
$user = "<<the account id>>"
Connect-MicrosoftTeams -AccountId $user
The problem here is that I'm always getting the prompt for device authentication:
Of course this would never work in a pipeline. How can I make this work? Can I use Token to connect with Teams?
You can publish an app using Teams App Submission API to enable it across organization level. Please refer to this documentation for more details.
There is an ability to install an app for a user using the Microsoft Graph, which might be a better approach. See https://learn.microsoft.com/en-us/graph/api/userteamwork-post-installedapps?view=graph-rest-1.0&tabs=http . You can also see the option to list apps, which might be useful to check IDs etc.: https://learn.microsoft.com/en-us/graph/api/userteamwork-list-installedapps?view=graph-rest-1.0&tabs=http

Pass stored Credential variable to new script

I have successfully built a working simple program that displays a menu and allows a user to choose which script to open and it runs in the same window.
I would like to be able to store credentials so that you would only need to enter them once while using this program and it passes them through to other scripts.
Please can someone advise.
I played around and found a solution. Seems the variables are carried through the session regardless if it runs a new script.
This bit of code sorted it out.
if ($cred -eq $null)
{
try {
Write-Host "`r`n"
$cred = Get-Credential -Credential $username
}
catch {
Write-Host -ForegroundColor Red "`r`nSomething has gone wrong with entering credentials. Please try run the script again or if issues persist please contact the system administrator.`r`n"
Return-ExitRestart
}

Powershell: Uninstall application by UpgradeCode

When I upgrade / downgrade my application via a Powershell script, I want to first force the uninstallation of the currently installed version before running the new installer.
How can I do that with Powershell, using the UpgradeCode of the application?
Doing it by application name would be less robust.
Since you mention upgrade code, it must mean that you are talking about an MSI file (Windows Installer). As stated by others such an uninstall is normally performed auto-magically by a properly authored MSI package - it is referred to as a major upgrade - which is essentially an uninstall of the existing version of a product and then the install of the newest version.
The Upgrade Table of the MSI being installed will specify what existing packages on the box will be uninstalled before the new version is installed. In theory you can uninstall any number of existing installations. You can even uninstall a competitive product if you are mad as a hatter. Frankly, and astonishingly, I have never tried to uninstall multiple products during one major upgrade - it is rarely called for. In most cases you uninstall a single, existing product and then install your latest version.
You can modify the Upgrade table using a transform to change how the major upgrade behaves - in other words to make it start or stop uninstalling a specific pre-existing installation.
You can also enumerate all related products that share the same upgrade code by calling this MSI API function (COM - VBScript used as sample):
Set installer = CreateObject("WindowsInstaller.Installer")
' Enumerate all products related to "Microsoft Visual C++ 2008 Redistributable - x86 9.0.30729.4148"
' {AA783A14-A7A3-3D33-95F0-9A351D530011} is the upgrade code
Set upgrades = installer.RelatedProducts("{AA783A14-A7A3-3D33-95F0-9A351D530011}")
For Each u In upgrades
MsgBox u, vbOKOnly, "Product Code: "
Next
Then you can uninstall the products by passing the product code(s) to the msiexec.exe command line (see below for how to do this via MSI API COM automation instead):
msiexec.exe /x {11111111-1111-1111-1111-11111111111X} /L*V "C:\msilog.log" REBOOT=ReallySuppress
Quick Parameter Explanation (since I recommend this option):
/X = run uninstall sequence
/QN = run completely silently
/L*V "C:\msilog.log"= verbose logging at path specified
{11111111-1111-1111-1111-11111111111X} = product guid of app to uninstall
REBOOT=ReallySuppress = prevent reboot without warning (badly authored MSI packages)
If you don't want to uninstall via msiexec.exe, then you can find a myriad of ways to invoke an MSI uninstall here:
Uninstalling an MSI file from the command line without using msiexec.
And you can find the product code of an installed MSI in several different ways: How can I find the product GUID of an installed MSI setup?
UPDATE: I guess I forgot the obvious, you can uninstall directly via MSI API automation. In the script below we get all products sharing the same upgrade code and then uninstall them in sequence.
Note that when run silently you should run with admin rights since the UAC may be suppressed and then the uninstall will usually fail (permission denied). Because of this the below script runs the uninstall interactively - allowing UAC prompting and elevation.
And if it isn't obvious: running this script will uninstall Orca! I use this product as a sample because it is quick to install again (hints on finding the installer quick if you need to towards bottom here - search for "orca"):
BIG DISCLAIMER:
The COM method installer.ConfigureProduct does not accept any arguments that allow us to pass in REBOOT=ReallySuppress. This means that a (very) badly authored package which triggers the ScheduleReboot action (or uses some more obscure magic to cause a reboot) - may reboot the system without warning if you run the below script with admin rights and in silent mode.
There is a newer call ConfigureProductEx which is available as a Win32 function, but it is not exposed via the COM automation interface. If you platform invoke you can use that call - there is a C++ example in section 14 here: Uninstalling an MSI file from the command line without using msiexec. Or you can use the DTF feature from the WiX toolkit (see section 6 in the same link as the C++ example).
UPDATE July 2018:
Set installer = CreateObject("WindowsInstaller.Installer")
installer.InstallProduct "product.msi", "REMOVE=ALL REBOOT=ReallySuppress"
Set installer = Nothing
Perhaps the above snippet is the best uninstall approach? This should suppress any reboots. I don't have the time or the setup to test it right now (on a Linux box), but I wanted to add it before I forget.
Original uninstall script:
Const msiUILevelNone = 2
Const msiInstallStateAbsent = 2
Set installer = CreateObject("WindowsInstaller.Installer")
'installer.UILevel = msiUILevelNone ' Disabled to prevent silent uninstall. Now the UAC prompt will show
' Uninstall Orca, replace upgrade code with yours
Set products = installer.RelatedProducts("{CFF4D510-79B2-1CCD-0061-5741A0565A76}")
For Each product In products
' MsgBox "Product Code: " & product ' Show the product code found, if you want
' The following call when run silently with admin rights may reboot the system without warning!
' This is due to badly authored MSI packages - most packages will not trigger this problem.
installer.ConfigureProduct product, 0, msiInstallStateAbsent ' Uninstall product
' See text above for info on the newer ConfigureProductEx method.
Next
Set installer = Nothing
MsgBox "Finished" ' Just so we know the script ran if nothing found to uninstall
Some Links:
Is there an alternative to GUID when using msiexec to uninstall an application? (uninstall by product name)
How can I uninstall an application using PowerShell?
How can I use powershell to run through an installer?
WIX (remove all previous versions)
Wix upgrade goes into maintenance mode and never does upgrade (various ways to uninstall, by product code, by upgrade code, etc...)
Since the question specifically mentions powershell I'll just put this here too. There are other PS solutions around using WMI and/or Get-Package. This solution is based off of https://outnull.wordpress.com/2016/11/02/uninstalling-application-based-on-upgradecode/ but accepts various forms of upgrade code syntax, and it tries to avoid string manipulation when converting to/from the package/upgrade Guid and the registry representation.
$upgradecode = "{CFF4D510-79B2-1CCD-0061-5741A0565A76}"
$installer = Join-Path -Path $env:SystemRoot -ChildPath "system32\msiexec.exe" -Resolve
function Reverse-Nibbles {
param ( [byte[]] $bytes )
# reverse nibbles of each byte
for($i = 0; $i -lt $bytes.Length; $i++ )
{
$bytes[$i] = (($bytes[$i] -band 0x0F0F) -shl 4) -bor (($bytes[$i] -band 0xF0F0) -shr 4)
}
Write-Output -NoEnumerate $bytes
}
function GuidToRegString {
param ( [guid] $guid )
$bigendian = (Reverse-Nibbles $guid.ToByteArray())
return [System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::new($bigendian).ToString()
}
function RegStringToGuid {
param ( [string] $guid )
$littleendian = (Reverse-Nibbles ([System.Runtime.Remoting.Metadata.W3cXsd2001.SoapHexBinary]::Parse($guid).Value))
return [guid]::new($littleendian)
}
$upcode = GuidToRegString ([guid]::Parse($upgradecode))
if (Test-Path -Path "HKLM:\Software\Classes\Installer\UpgradeCodes\$upcode") {
$products = RegStringToGuid (Get-Item -Path "HKLM:\Software\Classes\Installer\UpgradeCodes\$upcode").Property
foreach ($prod in $products) {
$pguid = [guid]::new($prod)
$p = $pguid.ToString("B")
if ((Test-Path -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\$p") -or
(Test-Path -Path "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\$p"))
{
$logfile = Join-Path -Path $PSScriptRoot -ChildPath uninstall-$($pguid.ToString("D")).log
$args = #( "/x", $p, "/l*v", """$logfile""", "/q", "COMPLETE_UNINSTALL=1", "REBOOT=REALLYSUPPRESS" )
Write-Host "Uninstalling $p"
$uninst = Start-Process -FilePath """$installer""" -ArgumentList $args -PassThru -Wait
Write-Host $uninst.ExitCode
}
}
}

Popup message for current user after script powershell

I created PowerShell script wich install an application on computer (windows 7).
This script is in GPO and deployed with GPO at logon users. This worked fine, but I want that at the end of installation, my powershell script send at the current logged user on computer a message like "Reboot your computer please".
I tested many things but I don'tview popup, maybe because my script are execute with admin rights (not with user rights).
Test :
#$wshell = New-Object -ComObject Wscript.Shell
#$wshell.Popup("Operation Completed",0,"Done",0x1)
[Windows.Forms.MessageBox]::Show(“My message”, , [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Information)
Your script may be popping up the message but then closing the PowerShell console immediately after, removing the popup. Try waiting on the result of the popup before closing the PowerShell instance:
$wshell = New-Object -ComObject Wscript.Shell
$result = $wshell.Popup("Operation Completed",0,"Done",0x1)
You need to load the assembly providing the MessageBox class first, and you cannot omit the message box title if you want to specify buttons and/or icons.
Add-Type -Assembly 'System.Windows.Forms'
[Windows.Forms.MessageBox]::Show(“My message”, "", [Windows.Forms.MessageBoxButtons]::OK, [Windows.Forms.MessageBoxIcon]::Information)
# ^^
You can use an empty string or $null here, but simply not providing a value (like you could do in VBScript) is not allowed.
As a side-note, I'd recommend avoiding typographic quotes in your code. Although PowerShell will tolerate them most of the time, they might cause issues sometimes. Always use straight quotes to be on the safe side.
Edit: Since you're running the script via a machine policy it cannot display message boxes to the logged-in user, because it's running in a different user context. All you can do is have a user logon script check whether the software is installed, and then display a message to the user. This works, because a user logon script running in the user's context.