so I want to delete an app of which I do not know the product ID. I know you can delete an app by using msiexec.exe /x and then the product ID. How would I go about getting the product ID of a specific app also in commandline and storing the value in a variable so I can just place the variable in the delete command?
Thank in advance!!
There's always get-package. No one knows about it but me. This should work for msi installs.
get-package *software* | uninstall-package
If you know the exact name, this should work. Uninstall-package doesn't take wildcards.
uninstall-package 'Citrix HDX RealTime Media Engine 2.9.400'
Sometimes, annoyingly, it prompts to install nuget first:
install-packageprovider nuget -force
If it's not an msi install, but a programs install, it takes a little more string mangling. You may have to add a '/S' or something for silent install at the end.
$prog,$myargs = -split (get-package 'Remote Support Jump Client *' |
% { $_.metadata['uninstallstring'] })
& $prog $myargs
Maybe in this case just run it:
& "C:\Program Files (x86)\Citrix\Citrix WorkSpace 2202\TrolleyExpress.exe" /uninstall /cleanup /silent
Or
$uninstall = get-package 'Citrix Workspace 2202' |
% { $_.metadata['uninstallstring'] }
$split = $uninstall -split '"'
$prog = $split[1]
$myargs = -split $split[2]
$myargs += '/silent'
& $prog $myargs
I think I have a way that works with or without double-quotes for non-msi installs:
$uninstall = get-package whatever | % { $_.metadata['uninstallstring'] }
$prog, $myargs = $uninstall | select-string '("[^"]*"|\S)+' -AllMatches |
% matches | % value
$prog = $prog -replace '"',$null
$silentoption = '/S'
$myargs += $silentoption # whatever silent uninstall option
& $prog $myargs
Here's a script I use. You have to know what the display name of the package is...like if you went to Remove Programs, what it's name would be. It works for my MSI packages that I create with WiX, but not all packages. You might consider winget command if you are on Windows 10+. winget has an uninstall option.
param (
[Parameter(Mandatory = $true)]
[string] $ProductName,
[switch] $Interactive = $false,
[switch] $key = $false
)
$log_directory = "c:\users\public"
# $log_directory = "c:\erase\logs"
if ((-not $Interactive) -and (-not (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)))
{
$interactive = $true
# echo "Not elevated, needs to be interactive"
}
$found = $null
$productsKeyName = "SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products"
$rootKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey($productsKeyName)
foreach ($productKeyName in $rootKey.GetSubKeyNames())
{
#$productKeyName
try
{
$installPropertiesKey = $rootKey.OpenSubKey("$productKeyName\InstallProperties")
if ($installPropertiesKey)
{
$displayName = [string] $installPropertiesKey.GetValue("DisplayName")
if ( (! [string]::IsNullOrEmpty($displayName)) -and ($displayName -eq $ProductName))
{
$found = $productKeyName
break
}
}
}
catch
{
}
finally
{
if ($installPropertiesKey) { $installPropertiesKey.Close() }
}
}
$rootKey.Close()
if (-not $found)
{
return "First search could not find $ProductName"
}
$localPackage = $null
if (! [string]::IsNullOrEmpty($found))
{
try
{
$regkey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("$productsKeyName\$found\InstallProperties")
$localPackage = $regkey.GetValue("LocalPackage")
}
catch
{
}
finally
{
if ($regkey) { $regkey.Close() }
}
}
if ($key)
{
return "Found key: $found"
}
if (![string]::IsNullOrEmpty($localPackage) -and (Test-Path $localPackage))
{
$logflags = "/lv*"
$logname = (Join-Path $log_directory "$ProductName_uninstall.log")
$args = #($logflags, $logname, "/X", $localPackage)
if (!$Interactive) { $args += "/q" }
&msiexec $args
}
else
{
"Could not find uninstall package: $ProductName"
}
Related
I need a script that terminates all RDP sessions of an AD user.
Only the username should be given, whereupon the script terminates all RDP sessions of this user (if necessary also enforces them).
Unfortunately, the Get-RDUserSession cmdlet does not work (the ConnectionBroker cannot be found).
Unfortunately, I cannot process the result of the CMD command qwinsta in PowerShell.
Any ideas or tips?
Thank you.
You can create custom objects from qwinsta's output, filter them and use rwinsta to kill the session.
Function Get-TSSessions
{
param (
[Parameter(Mandatory = $true, Position = 0 )]
[String]$ComputerName
) # End Parameter Block
qwinsta /server:$ComputerName |
ForEach-Object{
If($_ -notmatch "SESSIONNAME")
{
New-Object -TypeName PSObject -Property `
#{
"ID" = [Int]$_.SubString(41,05).Trim()
"ComputerName" = $Computer
"User" = $_.SubString(19,22).Trim()
"State" = $_.SubString(47,08).Trim()
}
}
}
} # End Function Get-TSSessions
Get-TSSessions -ComputerName <ServerName> |
Where-Object{$_.User -eq "SomeUser"} |
ForEach{ & "rwinsta /Server:$($_.ComputerName) $($_.ID)" }
Obviously, you can improve by wrapping up the rwinsta command in its own function. At the moment I only have reporting work written around this sort of thing, so in the spirit of answering the question without writing the whole thing, this should get you through.
Also, I believe there are a number of scripts and functions available for this on the PowerShell Gallery. In fact, I think there were functions Get/Stop-TerminalSession in the PowerShell Community Extensions, which you can install as a module.
param
(
[Parameter(Mandatory = $false,
HelpMessage = 'Specifies the user name (SamAccountName).',
DontShow = $false)]
[SupportsWildcards()]
[ValidateNotNullOrEmpty()]
[ValidateScript({
Import-Module -Name 'ActiveDirectory' -Force
if (Get-ADUser -Filter "sAMAccountName -eq '$_'") {
return $true
} else {
return $false
}
})]
[string]$Username = $env:USERNAME
)
$ErrorActionPreference = 'SilentlyContinue'
Import-Module -Name 'ActiveDirectory' -Force
foreach ($system in (Get-ADComputer -Filter ("Name -ne '$env:COMPUTERNAME' -and OperatingSystem -like 'Windows Server*'"))) {
[string]$system = $system.Name
$session = ((quser /server:$system | Where-Object {
$_ -match $Username
}) -split ' +')[3]
if ($session) {
logoff $session /server:$system
}
}
I'm new to PowerShell (PS). Currently I'm using windows server 2012 and I'm interested to know whether there is any way to export User Rights Assignment into a txt file. I tried
secedit /export /areas USER_RIGHTS /cfg d:\policies.txt
The above should should export it.
So, I get this: Current Output.
Is there any way to export User Rights Assignment and make it look like (even with using batch files): Expected Output.
P.S
Is There anyway to output those values in console? So i would be enable to redirect them to a txt file.
Here's a PowerShell script that outputs usable objects with translated names and SIDs:
#requires -version 2
# Fail script if we can't find SecEdit.exe
$SecEdit = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::System)) "SecEdit.exe"
if ( -not (Test-Path $SecEdit) ) {
Write-Error "File not found - '$SecEdit'" -Category ObjectNotFound
exit
}
# LookupPrivilegeDisplayName Win32 API doesn't resolve logon right display
# names, so use this hashtable
$UserLogonRights = #{
"SeBatchLogonRight" = "Log on as a batch job"
"SeDenyBatchLogonRight" = "Deny log on as a batch job"
"SeDenyInteractiveLogonRight" = "Deny log on locally"
"SeDenyNetworkLogonRight" = "Deny access to this computer from the network"
"SeDenyRemoteInteractiveLogonRight" = "Deny log on through Remote Desktop Services"
"SeDenyServiceLogonRight" = "Deny log on as a service"
"SeInteractiveLogonRight" = "Allow log on locally"
"SeNetworkLogonRight" = "Access this computer from the network"
"SeRemoteInteractiveLogonRight" = "Allow log on through Remote Desktop Services"
"SeServiceLogonRight" = "Log on as a service"
}
# Create type to invoke LookupPrivilegeDisplayName Win32 API
$Win32APISignature = #'
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LookupPrivilegeDisplayName(
string systemName,
string privilegeName,
System.Text.StringBuilder displayName,
ref uint cbDisplayName,
out uint languageId
);
'#
$AdvApi32 = Add-Type advapi32 $Win32APISignature -Namespace LookupPrivilegeDisplayName -PassThru
# Use LookupPrivilegeDisplayName Win32 API to get display name of privilege
# (except for user logon rights)
function Get-PrivilegeDisplayName {
param(
[String] $name
)
$displayNameSB = New-Object System.Text.StringBuilder 1024
$languageId = 0
$ok = $AdvApi32::LookupPrivilegeDisplayName($null, $name, $displayNameSB, [Ref] $displayNameSB.Capacity, [Ref] $languageId)
if ( $ok ) {
$displayNameSB.ToString()
}
else {
# Doesn't lookup logon rights, so use hashtable for that
if ( $UserLogonRights[$name] ) {
$UserLogonRights[$name]
}
else {
$name
}
}
}
# Outputs list of hashtables as a PSObject
function Out-Object {
param(
[System.Collections.Hashtable[]] $hashData
)
$order = #()
$result = #{}
$hashData | ForEach-Object {
$order += ($_.Keys -as [Array])[0]
$result += $_
}
New-Object PSObject -Property $result | Select-Object $order
}
# Translates a SID in the form *S-1-5-... to its account name;
function Get-AccountName {
param(
[String] $principal
)
if ( $principal[0] -eq "*" ) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($principal.Substring(1))
$sid.Translate([Security.Principal.NTAccount])
}
else {
$principal
}
}
$TemplateFilename = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
$LogFilename = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
$StdOut = & $SecEdit /export /cfg $TemplateFilename /areas USER_RIGHTS /log $LogFilename
if ( $LASTEXITCODE -eq 0 ) {
Select-String '^(Se\S+) = (\S+)' $TemplateFilename | Foreach-Object {
$Privilege = $_.Matches[0].Groups[1].Value
$Principals = $_.Matches[0].Groups[2].Value -split ','
foreach ( $Principal in $Principals ) {
Out-Object `
#{"Privilege" = $Privilege},
#{"PrivilegeName" = Get-PrivilegeDisplayName $Privilege},
#{"Principal" = Get-AccountName $Principal}
}
}
}
else {
$OFS = ""
Write-Error "$StdOut"
}
Remove-Item $TemplateFilename,$LogFilename -ErrorAction SilentlyContinue
in addition to Eric's change i also needed to add a try catch to one of the functions in Bill_Stewart's post. if the SID being translated is from an object that no longer exists this will return the SID instead of sending an error for translate.
# Translates a SID in the form *S-1-5-... to its account name;
function Get-AccountName {
param(
[String] $principal
)
if ( $principal[0] -eq "*" ) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($principal.Substring(1))
Try {$out = $sid.Translate([Security.Principal.NTAccount])}
catch
{
$out = $principal
}
$out
}
else {
$principal
}
}
Great script overall. Thank you for your efforts. One change I needed to make however to get it to output all principals assigned a right was to change the regex to '^(Se\S+) = (.+)' so that principals that were already resolved with a space in the name such as 'Domain users' were matched. Before that it would just report 'Domain.'
To save the output to a file, add a >> filename after the closing bracket of the last foreach-object
Ex:
}
} >> 'outFile.txt'
or to output as delimited file (e.g., csv) use the following:
} | convertto-csv -delimiter '~' -notypeinformation >> 'outFile.txt'
Hope this helps.
I have a file containing a large number of occurrences of the string Guid="GUID HERE" (where GUID HERE is a unique GUID at each occurrence) and I want to replace every existing GUID with a new unique GUID.
This is on a Windows development machine, so I can generate unique GUIDs with uuidgen.exe (which produces a GUID on stdout every time it is run). I have sed and such available (but no awk oddly enough).
I am basically trying to figure out if it is possible (and if so, how) to use the output of a command-line program as the replacement text in a sed substitution expression so that I can make this replacement with a minimum of effort on my part. I don't need to use sed -- if there's another way to do it, such as some crazy vim-fu or some other program, that would work as well -- but I'd prefer solutions that utilize a minimal set of *nix programs since I'm not really on *nix machines.
To be clear, if I have a file like this:
etc etc Guid="A" etc etc Guid="B"
I would like it to become this:
etc etc Guid="C" etc etc Guid="D"
where A, B, C, D are actual GUIDs, of course.
(for example, I have seen xargs used for things similar to this, but it's not available on the machines I need this to run on, either. I could install it if it's really the only way, although I'd rather not)
I rewrote the C# solution in PowerShell. I figured it would be easier for you to run a powershell script then compile a C# exe.
Steps for using this:
Download/install powershell
Save the code below somewhere, named GuidSwap.ps1
Modify the $filename and $outputFilename variables to suit your needs
Run powershell -noexit c:\location\to\guidswap.ps1
## GuidSwap.ps1
##
## Reads a file, finds any GUIDs in the file, and swaps them for a NewGUID
##
$filename = "d:\test.txt"
$outputFilename = "d:\test_new.txt"
$text = [string]::join([environment]::newline, (get-content -path $filename))
$sbNew = new-object system.text.stringBuilder
$pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}"
$lastStart = 0
$null = ([regex]::matches($text, $pattern) | %{
$sbNew.Append($text.Substring($lastStart, $_.Index - $lastStart))
$guid = [system.guid]::newguid()
$sbNew.Append($guid)
$lastStart = $_.Index + $_.Length
})
$null = $sbNew.Append($text.Substring($lastStart))
$sbNew.ToString() | out-file -encoding ASCII $outputFilename
Write-Output "Done"
I was looking for a way to replace all GUIDs in a Visual Studio solution, so I took the answer to this StackOverflow question (GuidSwap.ps1) and extended it so that the script keeps track of GUIDs that are referenced across multiple files. An example is shown in the header below.
<#
.Synopsis
Replace all GUIDs in specified files under a root folder, carefully keeping track
of how GUIDs are referenced in different files (e.g. Visual Studio solution).
Loosely based on GuidSwap.ps1:
http://stackoverflow.com/questions/2201740/replacing-all-guids-in-a-file-with-new-guids-from-the-command-line
.NOTES
Version: 1.0
Author: Joe Zamora (blog.idmware.com)
Creation Date: 2016-03-01
Purpose/Change: Initial script development
.EXAMPLE
.\ReplaceGuids.ps1 "C:\Code\IDMware" -FileNamePatterns #("*.sln","*.csproj","*.cs") -Verbose -WhatIf
#>
# Add common parameters to the script.
[CmdletBinding()]
param(
$RootFolder
,$LogFolder='.'
,[String[]]$FileNamePatterns
,[switch]$WhatIf
)
$global:WhatIf = $WhatIf.IsPresent
# Change directory to the location of this script.
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
cd $dir
$ScriptName = $MyInvocation.MyCommand.Name
If(!($RootFolder))
{
Write-Host #"
Usage: $ScriptName -RootFolder <RootFolder> [Options]
Options:
-LogFolder <LogFolder> Defaults to location of script.
-FileNamePatterns #(*.ext1, *.ext2, ...) Defaults to all files (*).
-WhatIf Test run without replacements.
-Verbose Standard Powershell flags.
-Debug
"#
Exit
}
if ($LogFolder -and !(Test-Path "$LogFolder" -PathType Container))
{
Write-Host "No such folder: '$LogFolder'"
Exit
}
<#
.Synopsis
This code snippet gets all the files in $Path that contain the specified pattern.
Based on this sample:
http://www.adminarsenal.com/admin-arsenal-blog/powershell-searching-through-files-for-matching-strings/
#>
function Enumerate-FilesContainingPattern {
[CmdletBinding()]
param(
$Path=(throw 'Path cannot be empty.')
,$Pattern=(throw 'Pattern cannot be empty.')
,[String[]]$FileNamePatterns=$null
)
$PathArray = #()
if (!$FileNamePatterns) {
$FileNamePatterns = #("*")
}
ForEach ($FileNamePattern in $FileNamePatterns) {
Get-ChildItem $Path -Recurse -Filter $FileNamePattern |
Where-Object { $_.Attributes -ne "Directory"} |
ForEach-Object {
If (Get-Content $_.FullName | Select-String -Pattern $Pattern) {
$PathArray += $_.FullName
}
}
}
$PathArray
} <# function Enumerate-FilesContainingPattern #>
# Timestamps and performance.
$stopWatch = [System.Diagnostics.Stopwatch]::StartNew()
$startTime = Get-Date
Write-Verbose #"
--- SCRIPT BEGIN $ScriptName $startTime ---
"#
# Begin by finding all files under the root folder that contain a GUID pattern.
$GuidRegexPattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}"
$FileList = Enumerate-FilesContainingPattern $RootFolder $GuidRegexPattern $FileNamePatterns
$LogFilePrefix = "{0}-{1}" -f $ScriptName, $startTime.ToString("yyyy-MM-dd_HH-mm-ss")
$FileListLogFile = Join-Path $LogFolder "$LogFilePrefix-FileList.txt"
$FileList | ForEach-Object {$_ | Out-File $FileListLogFile -Append}
Write-Host "File list log file:`r`n$FileListLogFile"
cat $FileListLogFile | %{Write-Verbose $_}
# Next, do a read-only loop over the files and build a mapping table of old to new GUIDs.
$guidMap = #{}
foreach ($filePath in $FileList)
{
$text = [string]::join([environment]::newline, (get-content -path $filePath))
Foreach ($match in [regex]::matches($text, $GuidRegexPattern)) {
$oldGuid = $match.Value.ToUpper()
if (!$guidMap.ContainsKey($oldGuid)) {
$newGuid = [System.Guid]::newguid().ToString().ToUpper()
$guidMap[$oldGuid] = $newGuid
}
}
}
$GuidMapLogFile = Join-Path $LogFolder "$LogFilePrefix-GuidMap.csv"
"OldGuid,NewGuid" | Out-File $GuidMapLogFile
$guidMap.Keys | % { "$_,$($guidMap[$_])" | Out-File $GuidMapLogFile -Append }
Write-Host "GUID map log file:`r`n$GuidMapLogFile"
cat $GuidMapLogFile | %{Write-Verbose $_}
# Finally, do the search-and-replace.
foreach ($filePath in $FileList) {
Write-Verbose "Processing $filePath"
$newText = New-Object System.Text.StringBuilder
cat $filePath | % {
$original = $_
$new = $_
$isMatch = $false
$matches = [regex]::Matches($new, $GuidRegexPattern)
foreach ($match in $matches) {
$isMatch = $true
$new = $new -ireplace $match.Value, $guidMap[$match.Value.ToString().ToUpper()]
}
$newText.AppendLine($new) | Out-Null
if ($isMatch) {
$msg = "Old: $original`r`nNew: $new"
if ($global:WhatIf) {
Write-Host "What if:`r`n$msg"
} else {
Write-Verbose "`r`n$msg"
}
}
}
if (!$global:WhatIf) {
$newText.ToString() | Set-Content $filePath
}
}
# Timestamps and performance.
$endTime = Get-Date
Write-Verbose #"
--- SCRIPT END $ScriptName $endTime ---
Total elapsed: $($stopWatch.Elapsed)
"#
Would you be open to compiling a C# console app to do this? I whipped this up real quick. It takes a filename as a command line argument, finds anything that looks like a GUID, replaces it with a new GUID, and writes the new contents of the file.
Take a look:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
namespace GUIDSwap
{
class Program
{
static int Main(string[] args)
{
try
{
if (args.Length == 0) throw new ApplicationException("No filename specified");
string filename = args[0];
filename = filename.TrimStart(new char[] { '"' }).TrimEnd(new char[] { '"' });
if (!File.Exists(filename)) throw new ApplicationException("File not found");
StreamReader sr = new StreamReader(filename);
string text = sr.ReadToEnd();
sr.Close();
StringBuilder sbNew = new StringBuilder();
string pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}";
int lastStart = 0;
foreach (Match m in Regex.Matches(text, pattern))
{
sbNew.Append(text.Substring(lastStart, m.Index - lastStart));
sbNew.Append(Guid.NewGuid().ToString());
lastStart = m.Index + m.Length;
}
sbNew.Append(text.Substring(lastStart));
StreamWriter sw = new StreamWriter(filename, false);
sw.Write(sbNew.ToString());
sw.Flush();
sw.Close();
return 0;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return 1;
}
}
}
}
you can just capture the uid into a variable first, then do the sed?
#echo off
setlocal enabledelayedexpansion
for /f %%x in ('uuidgen.exe') do (
set uid=%%x
)
sed -e "s/Guid=\"\(.*\)\"/Guid=\"!uid!\"/g" file
I really like the solution by BigJoe714. I took it one step further finding all specific extension files and replace all GUIDs.
<pre>
<code>
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace AllGuidSwap
{
class Program
{
static void Main(string[] args)
{
try
{
if (args.Length == 0) throw new ApplicationException("No filename specified");
string directory = args[0]; //Path
string extensionToFind = args[1]; //Extension to find
if (!Directory.Exists(directory)) throw new ApplicationException("directory not found");
var allFiles = Directory.GetFiles(directory).Where(a => a.EndsWith(extensionToFind));
foreach (var filename in allFiles)
{
if (!File.Exists(filename)) throw new ApplicationException("File not found");
var sr = new StreamReader(filename);
var text = sr.ReadToEnd();
sr.Close();
var sbNew = new StringBuilder();
var pattern = "[a-fA-F0-9]{8}-([a-fA-F0-9]{4}-){3}[a-fA-F0-9]{12}";
var lastStart = 0;
foreach (Match m in Regex.Matches(text, pattern))
{
sbNew.Append(text.Substring(lastStart, m.Index - lastStart));
sbNew.Append(Guid.NewGuid().ToString().ToUpperInvariant());
lastStart = m.Index + m.Length;
}
sbNew.Append(text.Substring(lastStart));
var sw = new StreamWriter(filename, false);
sw.Write(sbNew.ToString());
sw.Flush();
sw.Close();
}
Console.WriteLine("Successful");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
</code>
</pre>
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
Windows PowerShell is out a quite long time now. In comparison to the the good old windows shell it's much more powerful.
Are there any scripts you use to speed up and simplify your every day work as an developer? If you can do magic with PowerShell -> please share it with us!
Update
Not really a script, but also very useful are PowerShell Community Extensions. The package contains a lot of new Cmdlets and PowerShell modifications.
I put together a bunch of scripts to work with Subversion at the command line. Most of them just use the --xml option to put various information in object form. Here are a couple of examples:
function Get-SvnStatus( [string[]] $Path = ".",
[string] $Filter = "^(?!unversioned|normal|external)",
[switch] $NoFormat )
{
# powershell chokes on "wc-status" and doesn't like two definitions of "item"
[xml]$status = ( ( Invoke-Expression "svn status $( $Path -join ',' ) --xml" ) -replace "wc-status", "svnstatus" ) `
-replace "item=", "itemstatus="
$statusObjects = $status.status.target | Foreach-Object { $_.entry } | Where-Object {
$_.svnstatus.itemstatus -match $Filter
} | Foreach-Object {
$_ | Select-Object #{ Name = "Status"; Expression = { $_.svnstatus.itemstatus } },
#{ Name = "Path"; Expression = { Join-Path ( Get-Location ) $_.path } }
} | Sort-Object Status, Path
if ( $NoFormat )
{
$statusObjects
}
else
{
$statusObjects | Format-Table -AutoSize
}
}
function Get-SvnLog( [string] $Path = ".",
[int] $Revision,
[int] $Limit = -1,
[switch] $Verbose,
[switch] $NoFormat )
{
$revisionString = ""
$limitString = ""
$verboseString = ""
if ( $Revision )
{
$revisionString = "--revision $Revision"
}
if ( $Limit -ne -1 )
{
$limitString = "--limit $Limit"
}
if ( $Verbose )
{
$verboseString = "--verbose"
}
[xml]$log = Invoke-Expression "svn log $( $path -join ',' ) --xml $revisionString $limitString $verboseString"
$logObjects = $log.log.logentry | Foreach-Object {
$logEntry = $_
$logEntry | Select-Object `
#{ Name = "Revision"; Expression = { [int]$logEntry.revision } },
#{ Name = "Author"; Expression = { $logEntry.author } },
#{ Name = "Date";
Expression = {
if ( $NoFormat )
{
[datetime]$logEntry.date
}
else
{
"{0:dd/MM/yyyy hh:mm:ss}" -f [datetime]$logEntry.date
}
} },
#{ Name = "Message"; Expression = { $logEntry.msg } } |
Foreach-Object {
# add the changed path information if the $Verbose parameter has been specified
if ( $Verbose )
{
$_ | Select-Object Revision, Author, Date, Message,
#{ Name = "ChangedPaths";
Expression = {
$paths = $logEntry.paths.path | Foreach-Object {
$_ | Select-Object `
#{ Name = "Change";
Expression = {
switch ( $_.action )
{
"A" { "added" }
"D" { "deleted" }
"M" { "modified" }
"R" { "replaced" }
default { $_.action }
}
} },
#{ Name = "Path"; Expression = { $_."#text" } }
}
if ( $NoFormat )
{
$paths
}
else
{
( $paths | Sort-Object Change | Format-Table -AutoSize | Out-String ).Trim()
}
}
}
}
else
{
$_
}
}
}
if ( $NoFormat )
{
$logObjects
}
else
{
$logObjects | Format-List
}
}
I have these aliased to svns and svnl, respectively. I talk about a few others here.
I use this one all the time because Windows Explorer's search for file contents never works for me:
Get-ChildItem -Recurse -Filter *.extension |
Select-String -List somestring |
Format-Table filename,linenumber -AutoSize
Just replace "extension" with the file extension of the file type you're interested in (or remove the -Filter parameter entirely) and replace "somestring" with the text you want to find in the file.
It's not a script, but in general it's helpful to learn when you can short-cut parameters, both by name and position.
By name, PowerShell just needs enough to narrow it down to one. For example, gci -r works but gci -f might be either -filter or -force.
Values specified without a parameter label are applied positionally. So if you want to specify -filter you could either do this:
gci -r -fil *.cs
Or provide . positionally as -path so you can also specify -filter positionally:
gci -r . *.cs
Any time you see something with proper capitalization, it's an indication I've used TAB completion. You should learn which things PS will complete for you -- it's quite good in V2.
Any time you see aliases in lowercase, it's something I typed from memory. You should memorize it too.
# grep example - find all using statements
dir -r -fil *cs | ss using
# advanced version
dir -fil *cs -r | ss '^using[^\(]+' | gpv line | sort -unique
# figure out how to query for drive free space (emphasis on "figure out" -- I can never remember things like this)
gcm *drive*
help Get-PSDrive -full
Get-PSDrive | gm
# now use it
Get-PSDrive | ? { $_.free -gt 1gb }
# pretend mscorlib.dll is an assembly you're developing and want to do some ad-hoc testing on
$system = [system.reflection.assembly]::LoadFile("c:\blah\...\mscorlib.dll")
$system | gm
$types = $a.GetTypes()
$types | gm
$types | ? { $_.ispublic -and $_.basetype -eq [system.object] } | sort name
$sbType = $types | ? { $_.name -eq "StringBuilder" }
# now that we've loaded the assembly, we could have also done:
# $sbType = [system.text.stringbuilder]
# but we may not have known it was in the Text namespace
$sb = new-object $sbType.FullName
$sb | gm
$sb.Append("asdf")
$sb.Append("jkl;")
$sb.ToString()
Is there a simple way to hook into the standard 'Add or Remove Programs' functionality using PowerShell to uninstall an existing application? Or to check if the application is installed?
$app = Get-WmiObject -Class Win32_Product | Where-Object {
$_.Name -match "Software Name"
}
$app.Uninstall()
Edit: Rob found another way to do it with the Filter parameter:
$app = Get-WmiObject -Class Win32_Product `
-Filter "Name = 'Software Name'"
EDIT: Over the years this answer has gotten quite a few upvotes. I would like to add some comments. I have not used PowerShell since, but I remember observing some issues:
If there are more matches than 1 for the below script, it does not work and you must append the PowerShell filter that limits results to 1. I believe it's -First 1 but I'm not sure. Feel free to edit.
If the application is not installed by MSI it does not work. The reason it was written as below is because it modifies the MSI to uninstall without intervention, which is not always the default case when using the native uninstall string.
Using the WMI object takes forever. This is very fast if you just know the name of the program you want to uninstall.
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /qb" -Wait}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /qb" -Wait}
To fix up the second method in Jeff Hillman's post, you could either do a:
$app = Get-WmiObject
-Query "SELECT * FROM Win32_Product WHERE Name = 'Software Name'"
Or
$app = Get-WmiObject -Class Win32_Product `
-Filter "Name = 'Software Name'"
One line of code:
get-package *notepad* |% { & $_.Meta.Attributes["UninstallString"]}
function Uninstall-App {
Write-Output "Uninstalling $($args[0])"
foreach($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
$dname = $obj.GetValue("DisplayName")
if ($dname -contains $args[0]) {
$uninstString = $obj.GetValue("UninstallString")
foreach ($line in $uninstString) {
$found = $line -match '(\{.+\}).*'
If ($found) {
$appid = $matches[1]
Write-Output $appid
start-process "msiexec.exe" -arg "/X $appid /qb" -Wait
}
}
}
}
}
Call it this way:
Uninstall-App "Autodesk Revit DB Link 2019"
I found out that Win32_Product class is not recommended because it triggers repairs and is not query optimized. Source
I found this post from Sitaram Pamarthi with a script to uninstall if you know the app guid. He also supplies another script to search for apps really fast here.
Use like this: .\uninstall.ps1 -GUID
{C9E7751E-88ED-36CF-B610-71A1D262E906}
[cmdletbinding()]
param (
[parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$ComputerName = $env:computername,
[parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
[string]$AppGUID
)
try {
$returnval = ([WMICLASS]"\\$computerName\ROOT\CIMV2:win32_process").Create("msiexec `/x$AppGUID `/norestart `/qn")
} catch {
write-error "Failed to trigger the uninstallation. Review the error message"
$_
exit
}
switch ($($returnval.returnvalue)){
0 { "Uninstallation command triggered successfully" }
2 { "You don't have sufficient permissions to trigger the command on $Computer" }
3 { "You don't have sufficient permissions to trigger the command on $Computer" }
8 { "An unknown error has occurred" }
9 { "Path Not Found" }
9 { "Invalid Parameter"}
}
To add a little to this post, I needed to be able to remove software from multiple Servers. I used Jeff's answer to lead me to this:
First I got a list of servers, I used an AD query, but you can provide the array of computer names however you want:
$computers = #("computer1", "computer2", "computer3")
Then I looped through them, adding the -computer parameter to the gwmi query:
foreach($server in $computers){
$app = Get-WmiObject -Class Win32_Product -computer $server | Where-Object {
$_.IdentifyingNumber -match "5A5F312145AE-0252130-432C34-9D89-1"
}
$app.Uninstall()
}
I used the IdentifyingNumber property to match against instead of name, just to be sure I was uninstalling the correct application.
Here is the PowerShell script using msiexec:
echo "Getting product code"
$ProductCode = Get-WmiObject win32_product -Filter "Name='Name of my Software in Add Remove Program Window'" | Select-Object -Expand IdentifyingNumber
echo "removing Product"
# Out-Null argument is just for keeping the power shell command window waiting for msiexec command to finish else it moves to execute the next echo command
& msiexec /x $ProductCode | Out-Null
echo "uninstallation finished"
I will make my own little contribution. I needed to remove a list of packages from the same computer. This is the script I came up with.
$packages = #("package1", "package2", "package3")
foreach($package in $packages){
$app = Get-WmiObject -Class Win32_Product | Where-Object {
$_.Name -match "$package"
}
$app.Uninstall()
}
I hope this proves to be useful.
Note that I owe David Stetler the credit for this script since it is based on his.
Based on Jeff Hillman's answer:
Here's a function you can just add to your profile.ps1 or define in current PowerShell session:
# Uninstall a Windows program
function uninstall($programName)
{
$app = Get-WmiObject -Class Win32_Product -Filter ("Name = '" + $programName + "'")
if($app -ne $null)
{
$app.Uninstall()
}
else {
echo ("Could not find program '" + $programName + "'")
}
}
Let's say you wanted to uninstall Notepad++. Just type this into PowerShell:
> uninstall("notepad++")
Just be aware that Get-WmiObject can take some time, so be patient!
Use:
function remove-HSsoftware{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,
ValuefromPipeline = $true,
HelpMessage="IdentifyingNumber can be retrieved with `"get-wmiobject -class win32_product`"")]
[ValidatePattern('{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}}')]
[string[]]$ids,
[parameter(Mandatory=$false,
ValuefromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Computer name or IP adress to query via WMI")]
[Alias('hostname,CN,computername')]
[string[]]$computers
)
begin {}
process{
if($computers -eq $null){
$computers = Get-ADComputer -Filter * | Select dnshostname |%{$_.dnshostname}
}
foreach($computer in $computers){
foreach($id in $ids){
write-host "Trying to uninstall sofware with ID ", "$id", "from computer ", "$computer"
$app = Get-WmiObject -class Win32_Product -Computername "$computer" -Filter "IdentifyingNumber = '$id'"
$app | Remove-WmiObject
}
}
}
end{}}
remove-hssoftware -ids "{8C299CF3-E529-414E-AKD8-68C23BA4CBE8}","{5A9C53A5-FF48-497D-AB86-1F6418B569B9}","{62092246-CFA2-4452-BEDB-62AC4BCE6C26}"
It's not fully tested, but it ran under PowerShell 4.
I've run the PS1 file as it is seen here. Letting it retrieve all the Systems from the AD and trying to uninstall multiple applications on all systems.
I've used the IdentifyingNumber to search for the Software cause of David Stetlers input.
Not tested:
Not adding ids to the call of the function in the script, instead starting the script with parameter IDs
Calling the script with more then 1 computer name not automatically retrieved from the function
Retrieving data from the pipe
Using IP addresses to connect to the system
What it does not:
It doesn't give any information if the software actually was found on any given system.
It does not give any information about failure or success of the deinstallation.
I wasn't able to use uninstall(). Trying that I got an error telling me that calling a method for an expression that has a value of NULL is not possible. Instead I used Remove-WmiObject, which seems to accomplish the same.
CAUTION: Without a computer name given it removes the software from ALL systems in the Active Directory.
For Most of my programs the scripts in this Post did the job.
But I had to face a legacy program that I couldn't remove using msiexec.exe or Win32_Product class. (from some reason I got exit 0 but the program was still there)
My solution was to use Win32_Process class:
with the help from nickdnk this command is to get the uninstall exe file path:
64bit:
[array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString
32bit:
[array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString
you will have to clean the the result string:
$uninstallPath = $unInstallPathReg[0].UninstallString
$uninstallPath = $uninstallPath -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstallPath = $uninstallPath .Trim()
now when you have the relevant program uninstall exe file path you can use this command:
$uninstallResult = (Get-WMIObject -List -Verbose | Where-Object {$_.Name -eq "Win32_Process"}).InvokeMethod("Create","$unInstallPath")
$uninstallResult - will have the exit code. 0 is success
the above commands can also run remotely - I did it using invoke command but I believe that adding the argument -computername can work
For msi installs, "uninstall-package whatever" works fine. For non-msi installs (Programs provider), it takes more string parsing. This should also take into account if the uninstall exe is in a path with spaces and is double quoted. Install-package works with msi's as well.
$uninstall = get-package whatever | % { $_.metadata['uninstallstring'] }
# split quoted and unquoted things on whitespace
$prog, $myargs = $uninstall | select-string '("[^"]*"|\S)+' -AllMatches |
% matches | % value
$prog = $prog -replace '"',$null # call & operator doesn't like quotes
$silentoption = '/S'
$myargs += $silentoption # add whatever silent uninstall option
& $prog $myargs # run uninstaller silently
Start-process doesn't mind the double quotes, if you need to wait anyway:
# "C:\Program Files (x86)\myapp\unins000.exe"
get-package myapp | foreach { start -wait $_.metadata['uninstallstring'] /SILENT }
On more recent windows systems, you can use the following to uninstall msi installed software. You can also check $pkg.ProviderName -EQ "msi" if you like.
$pkg = get-package *name*
$prodCode = "{" + $pkg.TagId + "}"
msiexec.exe /X $prodCode /passive