Calculate days since last program installed using PowerShell - powershell

What is the way to find all recently installed/repaired/modified software?
What I have been trying is to get the list from the registry, then filter it to find only relevant programs, and then try to calculate which of them was installed before a certain threshold.
This is what I have been doing:
$Installed_Software=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate
$SoftwareInstall_Days = 40
$TheDate = (([datetime]::Now))
$Installed_recently= #()
$Installed_recently=($Installed_Software |
Where-Object {($_.DisplayName -match ('Something') -or $_.Publisher -match ('SomeOtherThing'))} |
where (($TheDate - $_.InstallDate) -le $SoftwareInstall_Days)
if ($Installed_recently) {
Write-Output "Relevant software was recently installed and/or repaired / modified.`nThese are the items:`n$Installed_recently"
}
else {
Write-Output "No relevant software was recently installed and/or repaired / modified."
}
Of course, the following condition is just an illustration of what I am failing to do:
where (($TheDate - $_.InstallDate) -le $SoftwareInstall_Days)
How can this be done?
Am I even in the right direction here?

In order to be able to compare dates, you need to parse the InstallDate string that you get from registry and then subtract it from the current date.
There are couple of formats that this string can be. On my machine, English Win10 x86, I see 2 date formats: yyyyMMdd and MM/dd/yyyy.
In the code below, I created an array that you can add additional formats to parse.
Here is the modified code - I used "Microsoft" string to test:
# Add additional property InstallDateObj that will hold the parsed DateTime object
$Installed_Software=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, InstallDateObj
$SoftwareInstall_Days = 40
$TheDate = (([datetime]::Now))
# Try to parse dates.
$Installed_Software.ForEach({
# add more formats if you need
[string[]] $formats = #("yyyyMMdd","MM/dd/yyyy")
$installDate = $_.InstallDate
$installedDateObj = $null;
$formats.ForEach({ [DateTime] $dt = New-Object DateTime; if([datetime]::TryParseExact($installDate, $_, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None, [ref]$dt)) { $installedDateObj = $dt} });
$_.InstallDateObj = $installedDateObj
})
$Installed_recently= #()
$Installed_recently=($Installed_Software |
Where-Object {($_.DisplayName -match ('Microsoft') -or $_.Publisher -match ('Microsoft') -and ($_.InstallDateObj -ne $null) -and ($TheDate - $_.InstallDateObj).Days -le $SoftwareInstall_Days)})
if($Installed_recently.Count -gt 0) {
Write-Output "Relevant software was recently installed and/or repaired / modified.`nThese are the items:`n"
Write-Output $Installed_recently
}
else {
Write-Output "No relevant software was recently installed and/or repaired / modified."
}

There are methods for datetime object to add or remove minutes,hours,days,years from datetime, see script below.
For your current exmaple of getting software from registry, InstallDate need to be converted to a datetime format and then compared to a needed date from report.
#Report date 40 days
$SoftwareInstall_Days = (([datetime]::Now)).AddDays(-40)
#Getting installed software and filtering based on report date
$Installed_recently=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, #{Name = "InstalledDate" ; Expression = {[DateTime]::ParseExact($_.InstallDate, 'yyyyMMdd', [Globalization.CultureInfo]::InvariantCulture) } }| where {$_.installeddate -ge $SoftwareInstall_Days}
#Output
$Installed_recently
Installed software also can be gathered from different places:
#Using WMI
Get-WmiObject -Class Win32_Product
#Using PS Software provider
Get-Package

Related

Proper way to export to CSV?

I had a quick questions regarding exporting to CSV.
How is it decided on what to export to a CSV? What do i mean by that? The below example is just reading off a list and querying the Systemversion off AD. Then, i am associating it with the proper version we go off by. Unfortunately, it does export it properly or sometimes at all. Im not sure if this is making any sense so pardon the ignorance on my part.
$CStrings = Get-Content "C:\users\ME\Desktop\Comps.txt"
$comps = Foreach($Computer in $CStrings){
$VS = Get-ADComputer -Identity:"CN=$Computer,OU=MY Computers,OU=LAB " -Properties:operatingSystemVersion | Select-Object -ExpandProperty operatingsystemversion
if($VS -like "21543"){
"$Computer", "Version 45.32"}
else {
if($VS -like "21544"){
"$Computer", "Version 45.33"}
else{
if($VS -like "21545"){
"$Computer", "Version 45.34"}
else{
"$Computer", "$VS"
}
}
}
}
Foreach($comp in $comps){
Write-Output $comp | Out-File C:\users\ME\Desktop\comps.csv
}
The result in the CSV comes out to just one version output, but nothing else. I tried "$computer - Versions 45.32" which joins it, and exports each computer but, its all in one column.
Before i get hounded at, i did check other forums including ms_docs lol would rather have someone dumb it down for me if possible.
Summary: Looking to keep the Computer Name, and Version in a separate Column.
Happy New Years BTW!(:
One of the easiest ways is to create a list of custom objects and then simply export that list to csv. Here is one way to do that with your current requirements.
$CStrings = Get-Content "C:\users\ME\Desktop\Comps.txt"
$comps = Foreach($Computer in $CStrings)
{
$VS = Get-ADComputer -Identity:"CN=$Computer,OU=MY Computers,OU=LAB " -Properties operatingSystemVersion | Select-Object -ExpandProperty operatingsystemversion
$VS = switch -regex ($VS)
{
'21543' {"Version 45.32"}
'21544' {"Version 45.33"}
'21545' {"Version 45.34"}
default {$_}
}
[PSCustomObject]#{
ComputerName = $Computer
OSVersion = $VS
}
}
$comps | Export-Csv -Path C:\users\ME\Desktop\comps.csv -NoTypeInformation

Property has a value but cannot select it

I have a function that checks the registry for an uninstall key called Get-InstalledApps
Function Get-InstalledApps {
param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[string]$NameRegex = ''
)
foreach ($comp in $ComputerName) {
$keys = '','\Wow6432Node'
foreach ($key in $keys) {
try {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
$apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
} catch {
continue
}
foreach ($app in $apps) {
$program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
$name = $program.GetValue('DisplayName')
if ($name -and $name -match $NameRegex) {
[pscustomobject]#{
ComputerName = $comp
DisplayName = $name
DisplayVersion = $program.GetValue('DisplayVersion')
Publisher = $program.GetValue('Publisher')
InstallDate = $program.GetValue('InstallDate')
UninstallString = $program.GetValue('UninstallString')
Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
Path = $program.name
}
}
}
}
}
}
and then I grab the DisplayName/Version for what I need. My current problem is that it only seems to work on certain machines. Example:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"}
Name Value
---- -----
InstallDate
ComputerName Computer
DisplayName Citrix Receiver 4.7
Bits 64
UninstallString C:\ProgramData\Citrix\Citrix Receiver 4.7\TrolleyExpress.exe /uninstall /cleanup
Path HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\CitrixOnlinePluginPackWeb
Publisher Citrix Systems, Inc.
DisplayVersion 14.7.0.13011
So this is great, I get what I want. Now I normally just pipe in | Select-Object Displayname -ExpandProperty Displayname and it would return "Citrix Receiver 4.7" just like I want. My problem is that on certain machines I'm getting this:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"} | Select-Object DisplayName
DisplayName
-----------
And that's it. Why is there no value listed? If I try to expandproperty I get an error because it says nothing is there, but clearly there is something there or the Where-Object would not have found it in my search. Again, in a lot cases this code works just fine and I get the value I want but on a lot of machines I'm getting what you see above.
Edited in from comments:
I run this on a user's machine and I get the results I posted. If I run it on my machine I'll get the value "Citrix Receiver 4.7" every time. Also, on my machine I don't get the Name and Value columns. Only about 1/4 of the machines I ran this code on actually gave me the value I expected. Windows 7 vs Windows 10 thing?
It looks to me like your function returns a [hashtable], but you're using it like it's an object with properties.
That happens to work fine with Where-Object because the .Member syntax works for accessing [hashtable] values, but it's not going to work with Select-Object because it's operating on actual properties.
So what can you do?
If you want to keep it as a [hashtable], and insist on doing it in a pipeline, you can use ForEach-Object:
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -Process { $_.DisplayName }
or
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -MemberName Item -ArgumentList DisplayName
Another thing you can do is change your function to return an object.
This is really easy to do with a [hashtable]; so say your function is about to return $hash, instead return:
New-Object -TypeName PSObject -Property $hash
Now you can use the normal suite of cmdlets and have them work as expected.
Edit: after seeing your code, it looks like you are converting your hashtable to an object already, but your output says otherwise. It wouldn't display as Name and Value columns if that were the case, so I still think something is wrong and the output is a [hashtable].
Edit 2: with info from comments about the platform differences, this seems to be happening because the object conversion is being done with the [pscustomobject] type accelerator which was added in PowerShell v3. Since the problematic machine is running Windows 7, it may be running v2 (which is what Win 7 shipped with).
Recommendations:
Get rid of Windows 7.
If you can't do that, upgrade PowerShell (Windows Management Framework) on that machine.
Either way, use New-Object as posted above.

PowerShell list installed third party application update

Is there a powershell command to return a list of installed non-microsoft application updates?
Example: after installing softwareA and installing update to softwareA -> one can see the installed update in the Control Panel - View Installed Updates section
Is there a PowerShell command or simple script to return true if update is installed or false if update is not installed?
Try this and replace the program name string with your program. Use wildcards to help narrow down the program name. An expected output will be the uninstall string last write time. Making the assumption that the last write time of the files are changing when there's a patch.
$programname = "Notepad++*"
$32bit = Get-ItemProperty
HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
$64bit = Get-ItemProperty
HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*
$programs = $32bit + $64bit
foreach ($program in $programs){
$program = Write-Output $program | where Displayname -like $programname
if ($program.DisplayName -ne $null) {
$LastModified = (Get-Item $program.uninstallstring).lastwritetime
$properties = #{
ProgramName = $program.DisplayName
Publisher = $program.Publisher
Version = $program.DisplayVersion
UninstallString = $program.UninstallString
LastModified = $LastModified
}
$obj = New-Object -TypeName PSObject -Property $properties
Write-Output $obj
}
}
Get-ItemProperty HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object { $_.ParentDisplayName -like "*foo*" }
This returns an object where ParentDisplayName is your program and DisplayVersion is the update version.

PowerShell script issue operators

I am attempting to use PowerShell to detect Intel NIC drivers, prior to deploying the updated drivers. I changed my script a bit to troubleshoot, to make sure I am capturing the correct driver versions, and eventually verifying proper use of operators (which I may not be).
#Change $DeployVersion to the driver version being deployed
#Change $INTELNICMODEL to the model of Intel NIC
#=======================================================
$DeployVersion = “12.15.31.0″
$INTELNICMODEL = “82579LM”
#Get IntelNIC Driver Version from Win32_PnPSignedDriver
$CurrentlyInstalledDriverVersion = Get-WmiObject Win32_PnPSignedDriver | Where-Object {$_.deviceclass -match “NET” -and $_.devicename -like “*$INTELNICMODEL*” -and $_.driverversion} | Select driverversion
Write $CurrentlyInstalledDriverVersion.driverversion
Write $DeployVersion
If ($CurrentlyInstalledVersion.driverversion -lt $DeployVersion)
{Write “need to update driver”}
else
{Write “driver is current”}
From a computer with $CurrentlyInstalledDriverVersion = $DeployVersion
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
need to update driver
Clearly, this is not right, so some testing…
change -lt to -gt
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
driver is current
…and then try -eq
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
driver is current
Then from a computer where $CurrentlyInstalledDriverVersion < $DeployVersion
OUTPUT
PS> .\testIntelNIC.ps1
12.6.47.1
12.15.31.0
need to update driver
..which looks good, but…
Change -lt to -gt
OUTPUT
PS> .\testIntelNIC.ps1
12.6.47.1
12.15.31.0
driver is current
Am I using the operators incorrectly? Or the if/else?
String comparisons don't know anything about the inner structure of your version strings. Cast the strings to Version objects and you'll be able to do proper comparisons. Note that you need to expand the DriverVersion property for the conversion to work.
[Version]$DeployVersion = "12.15.31.0"
...
[Version]$CurrentlyInstalledDriverVersion = Get-WmiObject Win32_PnPSignedDriver |
Where-Object {
$_.deviceclass -match "NET" -and
$_.devicename -like "*$INTELNICMODEL*" -and
$_.driverversion
} | Select -Expand driverversion

Formatting the output of repadmin - powershell

I'm creating a script that tells me the creation / modification date and other pieces of info of AD objects to determine upgrade status of the machines in large domains. I have no problem accomplishing this in a well formatted and easy to read manner in Server 2008 because it has Active Directory modules, but this isn't the case with Server 2003.
With server 2003 I had to use a different approach to the script to gather the information I want, but I am unsure how to format this.
This is my current script:
$filePath = “$ENV:UserProfile\Desktop\output.txt”
## Enter the name of the Domain controller below
$DCName = “Exchange”
$computers = dsquery computer domainroot -name * -limit 0
Foreach ($computer in $computers) {
repadmin /showattr $DCName $computer /atts:"WhenChanged,WhenCreated,OperatingSystem" /long | out-file –append $filepath
}
This is the sample output:
DN: CN=Sample-Object,CN=Computers,DC=Contoso,DC=com
1> whenCreated: 07/04/2011 14:00:02 Pacific Standard Time Pacific Daylight Time
1> whenChanged: 08/09/2012 11:24:22 Pacific Standard Time Pacific Daylight Time
1> operatingSystem: Windows 7 Professional
In server 2008 I'm able to use string formatting ('"{0}","{1}"' -F $computer.name, $computer.whatever) amd output it to a .csv to make it presentable but I don't think the same methods will apply to the results of repadmin.
My end goal would to simply have a CSV with Computer Name, along with the three or however many attributes I have extracted from repadmin.
Any help appreciated, thank you.
Give this a try, you can export it to CSV and import it back as objects:
$result = dsquery computer domainroot -name * -limit 0 | foreach {
repadmin /showattr $DCName $_ /atts:"WhenChanged,WhenCreated,OperatingSystem" /long
} | Out-String
$result.split([string[]]'DN:',[StringSplitOptions]::RemoveEmptyEntries) | Foreach-Object{
$attr = $_.Split("`r`n",[StringSplitOptions]::RemoveEmptyEntries)
New-Object -TypeName PSObject -Property #{
DN = $attr[0].Trim()
whenCreated = $attr[1].Trim() -replace '^1> whenCreated: '
whenChanged = $attr[2].Trim() -replace '^1> whenChanged: '
operatingSystem = $attr[3].Trim() -replace '^1> operatingSystem: '
}
}