Check if program is installed if so go to next powershell - powershell

I've googled and not found anything useful to me.
I have 4 msi files I want to install but would like to check if some of it is installed on the computer.
Example:
check if program 1 is installed, if not install it and go to and install program 2.
However if it's not installed, install it and go to program 2 and do the same test there.
Execute-MSI -Action Install -Path "$dirFiles\Program1"
Execute-MSI -Action Install -Path "$dirFiles\Program2"
Execute-MSI -Action Install -Path "$dirFiles\Program3"
Execute-MSI -Action Install -Path "$dirFiles\Program4"

If you know the GUID, you could test path the uninstall key. Also don't forget that if your OS is 64 bit, there will be the same key in WOW6432Node for 32 bit apps.
$uninstallkey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\"
$uninstall32key = "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"
#Example 64-bit app
$app1guid = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
if (!(Test-Path "$uninstallkey\$app1guid)) {Execute-MSI -Action Install -Path "$dirFiles\Program1"}
#Example 32-bit app
$app2guid = "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"
if (!(Test-Path "$uninstall32key\$app2guid)) {Execute-MSI -Action Install -Path "$dirFiles\Program1"}

Without knowing anything about your applications, there are two easy options I can think of.
1) Read a log file, looking for a pattern / string - check 6 times (configure as needed) and fail if it doesn't complete. If it does, move on to the next install and do the same check
$LOGFILE = 'C:\Somewhere.log'
$Complete = 'no'
$Counter = 1
$max = 6
Start-Sleep 10
DO {
$Check = SELECT-STRING -pattern 'status: 0.' -path $logfile -quiet
write-host $Check
If (($Check) -eq $True) {
Set-Variable -name Complete -Value "yes"
}
Else {Set-Variable -name Complete -Value 'no'}
Write-host $Counter
Start-Sleep 20
$Counter++
}
while ($Complete -eq 'no')
If (($Counter) -eq $max) {
Throw 'Installation failed, check the error log'
}
Option 2)
If you know what directories it creates or even a file count, can do something like the above with count instead of a file read
$PRDIR = "D:\Folder"
If (($PRDIR.Count) -gt 2)
{
Do something
}
Else
{
Do something else
{
Hope that helps!
R

You can use WMI to check if your MSIs are installed or not. Example:
$products = Get-WmiObject -Class win32_product | Where-Object { $_.Name -like "*someName*" } | Select-Object *
You might extend the Where-Object clause with additional patterns to query for (e.g. via $_.Name -like "*Sw1*" -or $_.Name -like "*Sw2*" ). $products should be an array including the findings, which you can use to check if the requested SW is installed or not. Example:
PS> $found = $results.Where({ $_.Name -like "*sw1*"})
PS> if ($found) { Write-Host "Found" }
Hope that helps

Related

Powershell Throw Causing Variables to Clear?

My PowerShell script just checks multiple servers to make sure the input* and output* directories are clear of any files.
I'm simply trying to output to console the results of a GCI call prior to throwing an error message. However, when I uncomment the "throw" line, the $inputFiles and $outputFiles no longer output to the console. Below is the code:
$allServers = #(
"server1.com",
"server2.com")
foreach ($server in $allServers) {
$inputFiles = Get-ChildItem -Path "\\$server\C$\jobs\statements\input*\" -Recurse | Where-Object {! $_.PSIsContainer } | Select FullName
$outputFiles = Get-ChildItem -Path "\\$server\C$\jobs\statements\output*\" -Recurse | Where-Object {! $_.PSIsContainer } | Select FullName
if ($inputFiles -eq $NULL -and $outputFiles -eq $NULL) {
Write-Host "Environment is ready for statement processing."
}
else {
Write-Host "Environment is NOT ready for statement processing."
Write-Host "The following files exist in input/output: `n"
$inputFiles
$outputFiles
#Throw "Files exist in input/output. See above for details."
}
}
Below is the console output:
Environment is NOT ready for statement processing.
The following files exist in input/output:
Environment is NOT ready for statement processing.
The following files exist in input/output:
FullName
--------
\\server1.com\C$\jobs\statements\input\asdasd.txt
\\server1.com\C$\jobs\statements\input_254\asdasd.txt
\\server1.com\C$\jobs\statements\input_test\asdasd.txt
\\server2.com\C$\jobs\statements\input\CUSSTAT10302021.245
\\server2.com\C$\jobs\statements\input\CUSSTAT11312021
\\server2.com\C$\jobs\statements\input\CUSSTAT11312021.zip
And below is the console output when I uncomment the "throw" line:
Environment is NOT ready for statement processing.
The following files exist in input/output:
Files exist in input/output. See above for details.
At C:\jobs\statements\bin\Statements-EnvironmentCheck.ps1:47 char:9
+ Throw "Files exist in input/output. See above for details."
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (Files exist in ...ve for details.:String) [], RuntimeException
+ FullyQualifiedErrorId : Files exist in input/output. See above for details.
I know I have some error output cleanup to perform in order to include all the servers that might have files present, but please ignore that for now.
What you're experiencing is explained in this answer and this answer, basically you need to implement Out-Host \ Out-Default:
$inputFiles, $outputFiles | Out-Host # Should fix the problem
# possibly `throw` might require this too
throw "Files exist in input/output. See above for details." | Out-Host
However, I feel is worth showing you a better way to approach your code, returning a unified array of objects which you can filter, sort and export.
$allServers = #(
"server1.com"
"server2.com"
)
$result = foreach ($server in $allServers) {
# use `-File` instead of `! $_.PSIsContainer`
$out = #{
in = Get-ChildItem "\\$server\C$\jobs\statements\input*\" -Recurse -File
out = Get-ChildItem "\\$server\C$\jobs\statements\output*\" -Recurse -File
}
# if $out['in'] and $out['out'] are `$null`, Ready is `$true`
[pscustomobject]#{
Ready = -not($out['in'] -or $out['out'])
Server = $server
Files = $out
}
}
Now, if you want to see which servers are Ready (no files in input and output):
$result.where{ $_.Ready }
And if you want to see which servers are not Ready, and have a list of the files:
$result.where{ -not $_.Ready }.foreach{
foreach($file in $_.Files.PSBase.Values.FullName) {
[pscustomobject]#{
Server = $_.Server
Files = $file
}
}
}

Compare directories exactly - including moved files

My aim is to compare two directories exactly - including the structure of the directories and sub-directories.
I need this, because I want to monitor if something in the folder E:\path2 was changed. For this case a copy of the full folder is in C:\path1. If someone changes something it has to be done in two directories.
It is important for us, because if something is changed in the directory (accidentally or not) it could break down other functions in our infrastructure.
This is the script I've already written:
# Compare files for "copy default folder"
# This Script compares the files and folders which are synced to every client.
# Source: https://mcpmag.com/articles/2016/04/14/contents-of-two-folders-with-powershell.aspx
# 1. Compare content and Name of every file recursively
$SourceDocsHash = Get-ChildItem -recurse –Path C:\path1 | foreach {Get-FileHash –Path $_.FullName}
$DestDocsHash = Get-ChildItem -recurse –Path E:\path2 | foreach {Get-FileHash –Path $_.FullName}
$ResultDocsHash = (Compare-Object -ReferenceObject $SourceDocsHash -DifferenceObject $DestDocsHash -Property hash -PassThru).Path
# 2. Compare name of every folder recursively
$SourceFolders = Get-ChildItem -recurse –Path C:\path1 #| where {!$_.PSIsContainer}
$DestFolders = Get-ChildItem -recurse –Path E:\path2 #| where {!$_.PSIsContainer}
$CompareFolders = Compare-Object -ReferenceObject $SourceFolders -DifferenceObject $DestFolders -PassThru -Property Name
$ResultFolders = $CompareFolders | Select-Object FullName
# 3. Check if UNC-Path is reachable
# Source: https://stackoverflow.com/questions/8095638/how-do-i-negate-a-condition-in-powershell
# Printout, if UNC-Path is not available.
if(-Not (Test-Path \\bb-srv-025.ftscu.be\DIP$\Settings\ftsCube\default-folder-on-client\00_ftsCube)){
$UNCpathReachable = "UNC-Path not reachable and maybe"
}
# 4. Count files for statistics
# Source: https://stackoverflow.com/questions/14714284/count-items-in-a-folder-with-powershell
$count = (Get-ChildItem -recurse –Path E:\path2 | Measure-Object ).Count;
# FINAL: Print out result for check_mk
if($ResultDocsHash -Or $ResultFolders -Or $UNCpathReachable){
echo "2 copy-default-folders-C-00_ftsCube files-and-folders-count=$count CRITIAL - $UNCpathReachable the following files or folders has been changed: $ResultDocs $ResultFolders (none if empty after ':')"
}
else{
echo "0 copy-default-folders-C-00_ftsCube files-and-folders-count=$count OK - no files has changed"
}
I know the output is not perfect formatted, but it's OK. :-)
This script spots the following changes successfully:
create new folder or new file
rename folder or file -> it is shown as error, but the output is empty. I can live with that. But maybe someone sees the reason. :-)
delete folder or file
change file content
This script does NOT spot the following changes:
move folder or file to other sub-folder. The script still says "everything OK"
I've been trying a lot of things, but could not solve this.
Does anyone can help me how the script can be extended to spot a moved folder or file?
I think your best bet is to use the .NET FileSystemWatcher class. It's not trivial to implement an advanced function that uses it, but I think it will simplify things for you.
I used the article Tracking Changes to a Folder Using PowerShell when I was learning this class. The author's code is below. I cleaned it up as little as I could stand. (That publishing platform's code formatting hurts my eyes.)
I think you want to run it like this.
New-FileSystemWatcher -Path E:\path2 -Recurse
I could be wrong.
Function New-FileSystemWatcher {
[cmdletbinding()]
Param (
[parameter()]
[string]$Path,
[parameter()]
[ValidateSet('Changed', 'Created', 'Deleted', 'Renamed')]
[string[]]$EventName,
[parameter()]
[string]$Filter,
[parameter()]
[System.IO.NotifyFilters]$NotifyFilter,
[parameter()]
[switch]$Recurse,
[parameter()]
[scriptblock]$Action
)
$FileSystemWatcher = New-Object System.IO.FileSystemWatcher
If (-NOT $PSBoundParameters.ContainsKey('Path')){
$Path = $PWD
}
$FileSystemWatcher.Path = $Path
If ($PSBoundParameters.ContainsKey('Filter')) {
$FileSystemWatcher.Filter = $Filter
}
If ($PSBoundParameters.ContainsKey('NotifyFilter')) {
$FileSystemWatcher.NotifyFilter = $NotifyFilter
}
If ($PSBoundParameters.ContainsKey('Recurse')) {
$FileSystemWatcher.IncludeSubdirectories = $True
}
If (-NOT $PSBoundParameters.ContainsKey('EventName')){
$EventName = 'Changed','Created','Deleted','Renamed'
}
If (-NOT $PSBoundParameters.ContainsKey('Action')){
$Action = {
Switch ($Event.SourceEventArgs.ChangeType) {
'Renamed' {
$Object = "{0} was {1} to {2} at {3}" -f $Event.SourceArgs[-1].OldFullPath,
$Event.SourceEventArgs.ChangeType,
$Event.SourceArgs[-1].FullPath,
$Event.TimeGenerated
}
Default {
$Object = "{0} was {1} at {2}" -f $Event.SourceEventArgs.FullPath,
$Event.SourceEventArgs.ChangeType,
$Event.TimeGenerated
}
}
$WriteHostParams = #{
ForegroundColor = 'Green'
BackgroundColor = 'Black'
Object = $Object
}
Write-Host #WriteHostParams
}
}
$ObjectEventParams = #{
InputObject = $FileSystemWatcher
Action = $Action
}
ForEach ($Item in $EventName) {
$ObjectEventParams.EventName = $Item
$ObjectEventParams.SourceIdentifier = "File.$($Item)"
Write-Verbose "Starting watcher for Event: $($Item)"
$Null = Register-ObjectEvent #ObjectEventParams
}
}
I don't think any example I've found online tells you how to stop watching the filesystem. The simplest way is to just close your PowerShell window. But I always seem to have 15 tabs open in each of five PowerShell windows, and closing one of them is a nuisance.
Instead, you can use Get-Job to get the Id of registered events. Then use Unregister-Event -SubscriptionId n to, well, unregister the event, where 'n' represents the number(s) you find in the Id property of Get-Job..
So basically you want to synchronize the two folders and note all the changes made on that:
I would suggest you to use
Sync-Folder Script
Or
FreeFile Sync.

Select option from Array

I am working on a side project and to make it easier for managment since almost all of out server names are 15 charactors long I started to look for an RDP managment option but none that I liked; so I started to write one and I am down to only one issue, what do I do to manage if the user types not enough for a search so two servers will match the Query. I think I will have to put it in an array and then let them select the server they meant. Here is what I have so far
function Connect-RDP
{
param (
[Parameter(Mandatory = $true)]
$ComputerName,
[System.Management.Automation.Credential()]
$Credential
)
# take each computername and process it individually
$ComputerName | ForEach-Object{
Try
{
$Computer = $_
$ConnectionDNS = Get-ADComputer -server "DomainController:1234" -ldapfilter "(name=$computer)" -ErrorAction Stop | Select-Object -ExpandProperty DNSHostName
$ConnectionSearchDNS = Get-ADComputer -server "DomainController:1234" -ldapfilter "(name=*$computer*)" | Select -Exp DNSHostName
Write-host $ConnectionDNS
Write-host $ConnectionSearchDNS
if ($ConnectionDNS){
#mstsc.exe /v ($ConnectionDNS) /f
}Else{
#mstsc.exe /v ($ConnectionSearchDNS) /f
}
}
catch
{
Write-Host "Could not locate computer '$Computer' in AD." -ForegroundColor Red
}
}
}
Basically I am looking for a way to manage if a user types server1
that it will ask does he want to connect to Server10 or Server11 since both of them match the filter.
Another option for presenting choices to the user is Out-GridView, with the -OutPutMode switch.
Borrowing from Matt's example:
$selection = Get-ChildItem C:\temp -Directory
If($selection.Count -gt 1){
$IDX = 0
$(foreach ($item in $selection){
$item | select #{l='IDX';e={$IDX}},Name
$IDX++}) |
Out-GridView -Title 'Select one or more folders to use' -OutputMode Multiple |
foreach { $selection[$_.IDX] }
}
else {$Selection}
This example allows for selection of multiple folders, but can you can limit them to a single folder by simply switching -OutPutMode to Single
I'm sure what mjolinor has it great. I just wanted to show another approach using PromptForChoice. In the following example we take the results from Get-ChildItem and if there is more than one we build a collection of choices. The user would select one and then that object would be passed to the next step.
$selection = Get-ChildItem C:\temp -Directory
If($selection.Count -gt 1){
$title = "Folder Selection"
$message = "Which folder would you like to use?"
# Build the choices menu
$choices = #()
For($index = 0; $index -lt $selection.Count; $index++){
$choices += New-Object System.Management.Automation.Host.ChoiceDescription ($selection[$index]).Name, ($selection[$index]).FullName
}
$options = [System.Management.Automation.Host.ChoiceDescription[]]$choices
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
$selection = $selection[$result]
}
$selection
-Directory requires PowerShell v3 but you are using 4 so you would be good.
In ISE it would look like this:
In standard console you would see something like this
As of now you would have to type the whole folder name to select the choice in the prompt. It is hard to get a unique value across multiple choices for the shortcut also called the accelerator key. Think of it as a way to be sure they make the correct choice!

Get Folder NTFS ACL on long path name

I have a PS script that will return NTFS ACLs if an individual user is assigned, works well until I hit a path exceeding 260 characters. I've found a lot of information on the path too long problem and some work-arounds but I'm struggling to integrate a solution into my script. Any suggestions?
Thanks!
$DateStart = Get-Date
$Path = "E:\"
$PermittedOU1 = "OU=Groups,dc=chiba,dc=localt"
$PermittedOU3 = "OU=System Accounts,OU=Accounts,dc=chiba,dc=local"
$PermittedACL1 = get-adgroup -Filter * -SearchBase $PermittedOU1
$PermittedACL3 = get-aduser -Filter * -SearchBase $PermittedOU3
$ObjectPathItem = Get-ChildItem -path $Path -Recurse | where-object {$_.PsIsContainer} | foreach- object -process { $_.FullName }
$howmany=0
$Logfilename = "C:\Users\administrator\Documents\$(get-date -f yyyy-MM-dd-hh-mm).csv"
Add-Content $Logfilename "$DateStart`n"
$totalfolders=0
$i=0
ForEach ($Folder in $ObjectPathItem)
{
$totalfolders++
}
Foreach ($Folder in $ObjectPathItem)
{
$ObjectACL = Get-ACL -Path $Folder
$i++
$howmany=0
Write-Progress -id 1 -Activity "Folder Recursion" -status "Folders Traversed: " -PercentComplete (($i / $totalfolders) * 100)
Foreach ($ACL in $ObjectACL.access)
{
$ACLstring = $ACL.identityreference.Value
$ACLstring = $ACLstring.Replace("CHIBA\","")
if (($ACLstring -notin $PermittedACL1.name)`
-and ($ACLstring -notin $PermittedACL3.SamAccountName)`
-and ($ACLstring -notin "NT AUTHORITY\SYSTEM") `
-and ($ACLstring -notin "BUILTIN\Administrators") `
-and ($ACLstring -notin "CREATOR OWNER"))
{
$newline = "`"$Folder`"" + "," + "$ACLString"
Add-Content $Logfilename "$newline"
$howmany+=1
}
else {
$howmany+=1
}
}
}
$DateEnd = Get-Date
Add-Content $Logfilename "`n`n$DateEnd"
One option you can usually use is to create a mapped drive using New-PSDrive. Something like:
Try{
$ObjectACL = Get-ACL -Path $Folder
}
Catch{
$SubPathLength = $Folder.FullName.substring(0,200).LastIndexOf('\')
$NewTempPath = $Folder.FullName.SubString(0,$SubPathLength)
New-PSDrive -Name Temp4ACL -Provider FileSystem -Root $NewTempPath
$ObjectACL = Get-ACL "Temp4ACL:$($Folder.FullName.SubSTring($SubPathLength,$Folder.FullName.Length-$SubPathLength))"
}
That will find the last \ before the 200th character in the path, grab a substring of the full path up to the end of that folder's name and create a temp drive of it, then get the ACL based off the temp drive and the remaining path. So this path:
C:\Temp\Subfolder\Really Long Folder Name\Another Subfolder\ABCDEFGHIJKLMNOPQRSTUVWXYZ\We Are Really Pushing It Now\Im Running Out Of Folder Name Ideas\Hello My Name Is Inigo Montoya\You Killed My Father Prepare To Die\ReadMe.txt
Gets cut at the second to last backslash. I would end up getting the ACL from:
Temp4ACL:\You Killed My Father Prepare To Die\ReadMe.txt
Easy way is to use "\\?" to support 32,767 characters.
$folder = "C:\MyFolder"
icacls "\\?\$folder"
https://msdn.microsoft.com/en-us/library/windows/desktop/aa364963(v=vs.85).aspx
In the ANSI version of this function, the name is limited to MAX_PATH characters. To extend this limit to 32,767 wide characters, call the Unicode version of the function (GetFullPathNameW), and prepend "\\?\" to the path.
Okay, this question is quite old but for those coming here as of today like myself I provide this information that I found through Google:
Microsoft Technet Script Center lists a "File System Security PowerShell Module" which claims that since version 3.0 it "leverages the AlphaFS (http://alphafs.codeplex.com) to work around the MAX_PATH limitation of 260 characters". At the time of this writing the module is at version 4.2.3.
The general idea of this module is described as "PowerShell only offers Get-Acl and Set-Acl but everything in between getting and setting the ACL is missing. This module closes the gap."
So without having tried this myself I suppose it should help in solving the OPs problem.
The module is also featured in a post by the "Hey, Scripting Guy! Blog".

check processor architecture and proceed with if statement

i know that here are a lot of examples how to get the processor architecture.
this should get the type with true or false checking on x64
my question is: how do i get this output into a if statement?
example: if it is an 64bit processor THEN perform a few steps and if it is 32bit then perform other steps. how can i go on further?
i tried a few versions of code but also got true or false back which is ok but how to go on further?
can you help me out guys?
thanks
thank you all.
i solved it by using the following:
$os_type = (Get-WmiObject -Class Win32_ComputerSystem).SystemType -match ‘(x64)’
if ($os_type -eq "True") {
Write-Host "i am an 64bit OS"
write-host $os_type }
else {
$os_type = (Get-WmiObject -Class Win32_ComputerSystem).SystemType -match ‘(x86)’
if ($os_type -eq "True") {
Write-Host "i am a 32 Bit OS" }
[System.Environment]::Is64BitProcess returns true or false, so it's a very simple if statement.
if ([System.Environment]::Is64BitProcess) {
# Do 64-bit stuff
} else {
#Do 32-bit stuff
}
You didn't specify which of the "lot of examples" you're using, so I showed the method I use.
Could also use the environment variables.
$Is64bitProcess = $env:PROCESSOR_ARCHITECTURE -eq 'AMD64'
$Is64bitOs = $Is64bit = $env:PROCESSOR_ARCHITEW6432 -eq 'AMD64'
thanks for your input!
i tried for example this:
Thank you for your answer! i tried for example the following:
echo "check if 32 or 64bit OS"
$var = (Get-WmiObject -Class Win32_ComputerSystem).SystemType -match ‘(x64)’
if ($var = "True") {echo "i am an 64bit OS"
#setting the current directory as the directory the script is in
$sourcenssm= Split-Path -Parent $MyInvocation.MyCommand.Definition
#setting the targetdirectory for nssm
$targetnssm="C:\Users\cp\Desktop\nssm.exe"
#setting the current directory as the directory the script is in
$sourcewts= Split-Path -Parent $MyInvocation.MyCommand.Definition
#setting the targetdirectory for wtswatchdog
$targetwts="C:\Windows\wtswatchdog.exe"
#copying nssm to target - WORKING
if (-not(Test-path $targetnssm)) {Copy-item -Path $sourcenssm\nssm.exe -Destination $targetnssm}
#copying wtswatchdog to target - CHECK!
if (-not(Test-path $targetwts)) {Copy-item -Path $sourcewts\wtswatchdog.exe -Destination $targetwts}
}
but it gives me everytime 64 bit as an output also if i am checking
echo "check if 32 or 64bit OS"
$var = (Get-WmiObject -Class Win32_ComputerSystem).SystemType -match ‘(x86)’
if ($var = "True") {echo "i am an 64bit OS"}
then it is also going to the echo-part.
Alroc's answer works for powershell 3.0 or above i believe. In case there is an issue with that another approach would be this:
$architecture = (Get-WmiObject win32_processor | Where-Object{$_.deviceID -eq "CPU0"}).AddressWidth
If ($architecture -eq 64) {
# Do 64-bit stuff
} else {
#Do 32-bit stuff
}