# Update these variables to match your system
$appName='Microsoft System Center Virtual Machine Manager Agent (x64)'
$desiredVersion='10.22.1287.0'
$msiFile='X:\System Center Virtual Machine Manager\amd64\Setup\msi\Agent\vmmAgent.msi'
$maxWaitSeconds=120
$computersList = @'
HYPERV001
HYPERV002
'@
function installMsiOnRemoteComputer{
param(
$computernames=$env:computername,
$msiFile,
$destinationLocalTempFolder='C:\Temp',
$testFileName='testfile.txt'
)
function translateLocalPathToSmbPath($computername,$localPath,$testFileName){
$adminDriveLetter=[regex]::match($localPath,'^([\w\W])\:').captures.groups[1].value
$partialPath=[regex]::match($localPath,'^([\w\W])\:(.*)').captures.groups[2].value
$testPath=join-path "\\$computername\$adminDriveLetter`$" "$partialPath"
if(!(test-path $testPath)){
try{
New-Item -Path $testPath -ItemType "directory" -force
}catch{
write-warning "Unable to create $testPath"
return $false
}
}
try{
$null=New-Item -Path $testPath -Name $testFileName -ItemType "file" -force
Remove-Item "$testPath\$testFileName" -force
return $testPath
}catch{
write-warning "Unable to read or write to $testPath"
return $false
}
}
$results=[hashtable]@{}
$msiLocalFilePath=join-path $destinationLocalTempFolder $(split-path $msiFile -leaf)
foreach ($computername in $computernames){
$translatedDestination=translateLocalPathToSmbPath $computername $destinationLocalTempFolder $testFileName
if($translatedDestination){
copy-item $msiFile $translatedDestination
}else{
write-warning "Unable to copy $msiFile to $translatedDestination"
$results+=[hashtable]@{$computername=$false}
continue
}
$psSession=new-psSession $computername
if($psSession.State -eq 'Opened'){
$result=invoke-command -session $pssession -scriptblock{
param($filePath)
$file=gi $filePath
$DataStamp = get-date -Format yyyyMMddTHHmmss
$logFile ="C:\" + '{0}-{1}.log' -f $file.name,$DataStamp
$MSIArguments = @(
"/i"
('"{0}"' -f $file.fullname)
"/qn"
"/norestart"
"/L*v"
$logFile
)
try{
[diagnostics.process]::start("msiexec.exe", $MSIArguments).WaitForExit()
write-host "MSIEXEC has been called for file $filePath on $env:computername"
return $true
}catch{
write-warning $_
return $false
}
} -Args $msiLocalFilePath
# Note Error:
# Resolved by not using -wait switch and calling [diagnostics.process] instead of Start-Process "msiexec.exe"
# Although, this error would still being thrown with the [diagnostics.process] result of success
# Processing data for a remote command failed with the following error message: The I/O operation has been aborted
# because of either a thread exit or an application request. For more information, see the about_Remote_Troubleshooting
# Help topic.
# + CategoryInfo : OperationStopped: (:String) [], PSRemotingTransportException
# + FullyQualifiedErrorId : JobFailure
# + PSComputerName :
$results+=[hashtable]@{$computername=$result}
remove-psSession $psSession
}else{
write-warning "Unable to connect to $computername"
$results+=[hashtable]@{$computername="Unable to connect to $computername"}
}
}
return $results
}
function removeAppwizProgram($computernames=$env:computername,$appName='Firefox'){
$results=[hashtable]@{}
foreach($computer in $computernames){
$session=new-pssession $computer
if($session.State -eq 'Opened'){
$result=invoke-command -session $session -scriptblock{
param($appName)
write-host "Checking $env:computername..."
try{
# Method 1: try using the Uninstall method of the application packager
$app=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
if($app.Name -eq $appName){
write-host "Uninstalling $app"
# pause
$null=$app.Uninstall()
$appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
if($appStillExists){
write-host "'$appName' still exists"
# Method 2: Using Registry
$uninstallStringRegPaths='HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
$uninstallStrings=Get-ChildItem -Path $uninstallStringRegPaths
$uninstallString=($uninstallStrings|Get-ItemProperty|Where-Object {$_.DisplayName -match $appName}).UninstallString
if($uninstallString.count -eq 1){
$appCode=[regex]::match($uninstallString,'\{(.*)\}').Value
$uninstallCommand="& msiexec.exe /x $appCode /quiet /norestart"
write-host "Invoking uninstall Command: $uninstallCommand"
Invoke-Expression $uninstallCommand
$appStillExists=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
if($appStillExists){
write-warning "Uninstall has been unsuccessful at removing $appName"
return $false
}else{
return $true
}
}else{
write-warning "Please check this/these uninstall string(s):`r`n$uninstallString"
return $false
}
}else{
write-host "'$appName' has been removed"
return $true
}
}else{
write-host "No matches for $appName"
return $true
}
}catch{
write-warning $_
return $false
}
} -Args $appName
$results+=@{$computer=$result}
remove-pssession $session
}else{
write-warning "Unable to connect to $computer"
$results+=@{$computer=$null}
}
}
return $results
}
function sortArrayStringAsNumbers([string[]]$names){
$hashTable=@{}
$maxLength=($names | Measure-Object -Maximum -Property Length).Maximum
foreach ($name in $names){
#[int]$x=.{[void]($name -match '(?:.(\d+))+$');$matches[1]}
#$x=.{[void]($name -match '(?:.(\d+)+)$');@($name.substring(0,$name.length-$matches[1].length),$matches[1])}
$originalName=$name
$x=.{Clear-Variable matches
[void]($name -match '(?:.(\d+)+)\w{0,}$');
if($matches){
[int]$trailingNonDigits=([regex]::match($name,'\D+$').value).length
if($trailingNonDigits){
$name=$name.substring(0,$name.length-$trailingNonDigits)
}
return ($name.substring(0,$name.length-$matches[1].length))+$matches[1].PadLeft($maxLength,'0');
}else{
return $name+''.PadLeft($maxLength,'0');
}}
$hashTable.Add($originalName,$x)
}
$sorted=foreach($item in $hashTable.GetEnumerator() | Sort Value){$item.Name}
return $sorted
}
function main{
$computerNames=sortArrayStringAsNumbers(@($computersList -split "`n" -replace "\..*$")|%{$_.tostring().trim()})
$results=[hashtable]@{}
foreach($node in $computernames){
write-host "Processing $node ..."
$appVersionPassed=invoke-command -computername $node {
param($appName,$desiredVersion)
$matchedApp=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
if($matchedApp.Version -eq $desiredVersion){
return $true
}else{
return $false
}
} -Args $appName,$desiredVersion
if(!$appVersionPassed){
$vcredist140Installed=invoke-command -computername $node {
$null=choco install vcredist140 -y
$chocoApps=choco list -l
if($chocoApps -match 'vcredist140'){
return $true
}else{
return $false
}
}
if($vcredist140Installed){
$appRemoved=removeAppwizProgram $node $appName
if(($appRemoved|out-string) -match "True"){
try{
installMsiOnRemoteComputer $node $msiFile
}catch{
write-warning $_
}
write-host "Now waiting up to $maxWaitSeconds seconds before checking on the install result"
$timer=[System.Diagnostics.Stopwatch]::StartNew()
do{
start-sleep -seconds 5
$appVersionPassed=invoke-command -computername $node {
param($appName,$desiredVersion)
$matchedApp=Get-WmiObject -Class Win32_Product -Filter "Name='$appName'"
if($matchedApp.Version -eq $desiredVersion){
return $true
}else{
return $false
}
} -Args $appName,$desiredVersion
if($appVersionPassed){
write-host "$appName $desiredVersion installed on $node successfully"
$results+=[hashtable]@{$node='$appName $desiredVersion installed'}
}
$exitCondition=$timer.elapsed.totalseconds -ge $maxWaitSeconds
}until($appVersionPassed -or $exitCondition)
if(!$appVersionPassed){
write-host "$appName $desiredVersion has NOT been installed successfully on $node"
$results+=[hashtable]@{$node='$appName $desiredVersion NOT installed'}
}
$timer.stop()
}else{
write-warning "Unable to uninstall outdated app on $node"
$results+=[hashtable]@{$node='Unable to uninstall outdated app'}
}
}else{
$results+=[hashtable]@{$node='Unable to install vcredist140'}
}
}else{
write-host "$appName is already at $desiredVersion"
$results+=[hashtable]@{$node='$appName is already at $desiredVersion'}
}
}
return sortArrayStringAsNumbers($results.GetEnumerator()|select Name,Value)
}
main
Categories: