Powershell script check application versions on multiple machines. - windows

Im currently running the following commands, but im running them through a foreachloop. I can currently output them both to a csv as just a list for each machine, but ideally id like to output them to a csv of all the machines in the loop, that has the apps listed along the top, then below the computername, version, etc. so that i can append the csv and just have one big csv that i can sort and filter when opened in excel.
Aditionally id also like to be able to alert if app versions are differnt than my baseline, not sure where to start on this though.
$InstalledApps = gwmi Win32Reg_AddRemovePrograms64 | Select DisplayName, Publisher, Version
$InstalledApps +=
C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe -Command {gwmi Win32Reg_AddRemovePrograms | Select DisplayName, Publisher, Version}
$InstalledApps = $InstalledApps | sort displayname | Get-Unique -AsString
$InstalledApps| Select DisplayName, Publisher, Version | ogv -PassThru | export-csv .\apps.csv
Thanks

Powershell v3. and above.
Try to use CimInstance instead of WMI where possible.
#Define a Table Array
$MasterTable = #()
#Define computer list
$Computerlist = "computer1", "computer22"
#Run Foreach to retrieve data from each computer remotely
Foreach ($computer in $Computerlist) {
$MasterTable += Get-CimInstance win32_product -Computername $Computer |
Select-Object #{Name = "Computername"; Expression = {$Computer}},
#{Name = "SoftwareName"; Expression = {$_.Caption}},
#{Name = "Publisher"; Expression = {$_.Vendor}},
#{Name = "Version"; Expression = {$_.Version}}
}
$MasterTable | export-csv .\apps.csv -NoTypeInformation -Append
This should give you one CSV export with four columns containing all the information you require (incl the computername Get-CimInstance was run against).
You can do some additional logic with the MasterTable Variable if you like before exporting to CSV

Yes, this is also possible using WMI, however I would suggest you look at migrating WMI calls to CimInstance instead using Powershell v.3 and above as WMI is legacy and is not really firewall friendly as CimInstance is for example.
But here is the code for using WMI.
#Define a Table Array
$MasterTable = #()
#Define computer list
$Computerlist = "computer1", "computer2"
#Run Foreach to retrieve data from each computer remotely
Foreach ($computer in $Computerlist) {
$MasterTable += Get-WmiObject -Class Win32_Product -Computername $Computer |
Select-Object #{Name = "Computername"; Expression = {$Computer}},
#{Name = "SoftwareName"; Expression = {$_.Caption}},
#{Name = "Publisher"; Expression = {$_.Vendor}},
#{Name = "Version"; Expression = {$_.Version}}
}
$MasterTable | export-csv c:\temp\apps.csv -NoTypeInformation -Append

Related

CSV comparison of Windows Services

I'm fairly new to Powershell and I'm stuck with this portion of a much larger script. I need to pull all Windows Services and compare them to see if their Startup Type of Status has changed. If there were any changes, I need to count them so I can put that value in the body of an e-mail. Also, I need to attach an HTML report showing the previous and current state of the Windows services that changed.
What I've done is the following:
That piece of code generates a CSV file showing the current state of the services.
Get-Service | Select-Object -Property Name,DisplayName,StartType,ServiceType,Status | Export-Csv -Path "C:\logs\after.csv"
Then i declare two variables, one for the current state, another one for the "template", the desired state of all Windows Services.
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
Then, i compare both of them, parsing only those service that've changed and generate a CSS styled HTML report based on that
Compare-Object $before $after -Property Name,DisplayName,StartType,ServiceType,Status | ConvertTo-html -Head $css | Set-Content "C:\logs\comparison.html"
This is what i get:
This is what it should look like:
Basically, i want to show the status of the latter CSV report in a new column after the Status column of the original CSV report. And I would also like to make a row count after that, so I can send an e-mail reporting HOW MANY services suffered any changes.
Any help will be deeply appreciated.
You could use Group-Object after Compare-Object and parse out the columns you need from that.
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
# find the differences in the StartType and Status columns. Use -PassThru to be able to process further
$groups = Compare-Object -DifferenceObject $before -ReferenceObject $after -Property StartType, Status -PassThru |
Sort-Object Name | Group-Object Name
$result = foreach ($group in $groups) {
$refGroup = $after | Where-Object { $_.Name -eq $group.Name }
# output an object with new StartType_* and Status_* columns and capture that in variable $result
$group.Group[0] |
Select-Object *, #{Name = 'StartType_Before'; Expression = {$_.StartType}},
#{Name = 'StartType_After'; Expression = {$refGroup.StartType}},
#{Name = 'Status_Before'; Expression = {$_.Status}},
#{Name = 'Status_After'; Expression = {$refGroup.Status}} -ExcludeProperty StartType,Status, SideIndicator
}
# now convert the $result to HTML and add a summary line with the number of services that have changed
$result | ConvertTo-Html -Head $css -PostContent "<br />Services affected: $($result.Count)" |
Set-Content "C:\logs\comparison.html"
If you also want output in the console do:
$result | Format-Table -AutoSize
Of course it is also possible to not use Compare-Object and do like below (will be slower, but easier to understand I guess):
$before = Import-Csv -Path "C:\logs\before.csv"
$after = Import-Csv -Path "C:\logs\after.csv"
$result = foreach ($item in $before) {
$diff = $after | Where-Object { $_.Name -eq $item.Name -and
($_.StartType -ne $item.StartType -or $_.Status -ne $item.Status) }
if ($diff) {
$item | Select-Object *, #{Name = 'StartType_Before'; Expression = {$item.StartType}},
#{Name = 'StartType_After'; Expression = {$diff.StartType}},
#{Name = 'Status_Before'; Expression = {$item.Status}},
#{Name = 'Status_After'; Expression = {$diff.Status}} -ExcludeProperty StartType,Status
}
}
# output to console
$result | Format-Table -AutoSize
# convert to HTML
$result | ConvertTo-Html -Head $css -PostContent "<br />Services affected: $($result.Count)" |
Set-Content "C:\logs\comparison.html"
Output on screen will look something like
Name DisplayName ServiceType StartType_Before StartType_After Status_Before Status_After
---- ----------- ----------- ---------------- --------------- ------------- ------------
AarSvc_8246b1 Agent Activation Runtime_8246b1 224 Manual Automatic Stopped Stopped
AdobeARMservice Adobe Acrobat Update Service Win32OwnProcess Automatic Automatic Running Stopped
ALG Application Layer Gateway Service Win32OwnProcess Manual Automatic Stopped Stopped
WdNisSvc Microsoft Defender Antivirus Network Inspection Service Win32OwnProcess Manual Manual Running Stopped

Failing to find DC servers and their disk usage

so i got stuck with this assignment where i need to find all DC servers and their disk ussage
i tried first to get all the DC servers and then add the disk usage info but it doesn't work
$getdomain = [System.Directoryservices.Activedirectory.Domain]::GetCurrentDomain()
$getdomain | ForEach-Object {$_.DomainControllers} |
ForEach-Object {
$hEntry= [System.Net.Dns]::GetHostByName($_.Name)
New-Object -TypeName PSObject -Property #{
Name = $_.Name
IPAddress = $hEntry.AddressList[0].IPAddressToString
}
} | get-wmiobject -class win32_logicaldisk | select-object pscomputername,deviceid,freespace,size
For the task you have you have some useless code lines in your code. Did you copy the code from somewhere? ;-)
I'd recommend to take your time to start learning the very basics of Powershell first.
That's actually all you need:
$DCList = [System.Directoryservices.Activedirectory.Domain]::GetCurrentDomain().DomainControllers
foreach ($DC in $DCList) {
Get-CimInstance -ClassName win32_logicaldisk -ComputerName $DC.Name |
Select-Object -Property pscomputername, deviceid, freespace, size
}
Conclusion: You should remove what you have inside the loop from your copied code and replace it with what you have added at the end. ;-)

How to get every disk in Windows and filter on DeviceType and then add the size of the disk next to it, separated by a comma?

I'm writing an PowerShell script to collect some data of a computer. I'm almost done, but I don't know how to get size of all the disks on the computer. I know how to do it with a couple of If statements, but I want it to automatically detect the drives, not that I have to write a new If statement if a new disk is attached. The output I want is as follows: "A:,250GB". The "A:," bit works, but not the disk size bit.
This is the code I used and tweaked, but to no avail:
$Drives = Get-WmiObject Win32_logicaldisk| ?{$_.DriveType -eq 3} | ForEach-Object {$_.name}
ForEach ($Drivename in $Drives) {
$Drivenames = Get-WMIObject -Query "Select * From win32_logicaldisk Where DriveType = '3'" -computer localhost | Select-Object DeviceID
$Drive = [Math]::Round($Drivenames.Size / 1GB)
"$Drivenames,", $Drive | Out-File "C:\HDS\HDS_DRIVES.csv" -Append
}
In addition, [Math]::Round($Drivenames.Size / 1GB) throws me an error:
Method invocation failed because [System.Object[]] does not contain a method named 'op_Division'"
You can use Calculated Property with Select-Object to make it much more simple:
Get-WmiObject Win32_logicaldisk| ? {$_.DriveType -eq 3} |
Select-Object #{N="DeviceId";E={$_.DeviceId}},`
#{N="VolumeName";E={$_.VolumeName}},`
#{N="Size";E={[Math]::Round($_.Size / 1GB)}} |
Out-File "C:\HDS\HDS_DRIVES.csv" -Append
Note that you don't need to Invoke Get-WmiObject Twice like in your example.
Why it doesn't work?
The issue is that $Drivenames contains only DeviceID (as you used Select-Object to get only that property). Therefore you're getting an error where trying to round it (as rounding nothing is not supposed to work).
How to fix it?
You have to add Size and then access it using .PropertyName:
$DriveInfo = Get-WMIObject -Query "Select * From win32_logicaldisk Where DriveType = '3'" -computer localhost | Select-Object DeviceID, Size
$DriveInfo | ForEach-Object {
$DriveSize = [Math]::Round($_.Size / 1GB)
"$($_.DeviceID),$DriveSize" | Out-File "C:\HDS\HDS_DRIVES.csv" -Append
}
How can I make it more elegant
Also, take a look at #Avshalom's answer which uses calculated property.

Save the powershell's output in a CSV file

I have a powershell script in which i can get information about my operating system (windows version, build...). However all that information is shown in the powershell console and I want them to be exported to a CSV or a XML file.
The script is :
Get-CimInstance Win32_OperatingSystem |
Select-Object Caption, CSDVersion, ServicePackMajorVersion, BuildNumber |
FL
Use Export-Csv cmdlet:
Get-CimInstance Win32_OperatingSystem | Select-Object Caption, CSDVersion, ServicePackMajorVersion, BuildNumber | Export-Csv -NoTypeInformation -Path .\OS_Info.csv
Result (OS_Info.csv):
"Caption","CSDVersion","ServicePackMajorVersion","BuildNumber"
"Microsoft Windows Server 2012 R2 Datacenter",,"0","9600"
Thank you it worked, the file is generated in the folder System32
As Rohin Sidharth mentioned, .\ prefix for the path will create file in the current dir ($PWD in PowerShell). You probably run PowerShell as administrator: in this case the default directory is %WinDir%\System32. Just use full path or GetFolderPath .Net method to get common folder path, like desktop:
... | Export-Csv -NoTypeInformation -Path 'C:\OS_Info.csv'
... | Export-Csv -NoTypeInformation -Path (Join-Path -Path [System.Environment]::GetFolderPath('Desktop') -ChildPath 'OS_Info.csv')
Can you also show me how to export many results in the same file ? for
example i have a script in which i can know all the update that are
installed :
Get-Hotfix | Select HotfixID,Description,InstalledOn | Sort InstalledOnfunction
and i want the results saves in the same CSV file
You can do this by using Select-Object's calculated properties:
# Get OS info
$OsInfo = Get-CimInstance -ClassName Win32_OperatingSystem
Get-Hotfix | # Get HotFixes
Sort-Object -Property InstalledOnfunction | # Sort them
Select-Object -Property #( # Select required fields
# Add Caption property from $OsInfo variable
#{
Name = 'Caption'
Expression = {$OsInfo.Caption}
}
# Add CSDVersion property from $OsInfo variable
#{
Name = 'CSDVersion'
Expression = {$OsInfo.CSDVersion}
}
# Add ServicePackMajorVersion property from $OsInfo variable
#{
Name = 'ServicePackMajorVersion'
Expression = {$OsInfo.ServicePackMajorVersion}
}
# Add BuildNumber property from $OsInfo variable
#{
Name = 'BuildNumber'
Expression = {$OsInfo.BuildNumber}
}
# Add other properties from original HotFix object
'HotfixID'
'Description'
'InstalledOn'
) | Export-Csv -NoTypeInformation -Path 'C:\OS_Info.csv'
You can also try to join objects using custom function.
Quick tip: Make sure you don't pipe to Format List (FL) then pipe to export-csv or you'll open the CSV file and your data will look like this.
ClassId2e4f51ef21dd47e99d3c952918aff9cd pageHeaderEntry pageFooterEntry
autosizeInfo shapeInfo groupingEntry
033ecb2bc07a4d43b5ef94ed5a35d280
Microsoft.PowerShell.Commands.Internal.Format.ListViewHeaderInfo
9e210fe47d09416682b841769c78b8a3
27c87ef9bbda4f709f6b4002fa4af63c
4ec4f0187cb04f4cb6973460dfe252df
cf522b78d86c486691226b40aa69e95c

Trying to display SMS_Client methods for SCCM using VBScript

This is the script I have in Powershell;
$wmi = get-wmiobject -Namespace root\ccm -class sms_client -list | gm
The line above works exactly how I want.
The line below gives me completely different results because I removed the -list switch:
$wmi = get-wmiobject -Namespace root\ccm -class sms_client | gm
My question is how can I transpose the first command to VBScript. I want to be able to call the "TriggerSchedule" method.
Something like this should work (schedule ID taken from the documentation):
Set wmi = GetObject("winmgmts://./root/ccm")
scheduleID = "{00000000-0000-0000-0000-000000000001}"
For Each client In wmi.ExecQuery("SELECT * FROM SMS_Client")
client.TriggerSchedule(scheduleID)
Next
Untested, though, because I don't have SCCM at hand here.
Late to the party but try this in PowerShell:
GWMI CCM_Scheduler_ScheduledMessage -namespace root\ccm\policy\machine\actualconfig |
select-object ScheduledMessageID, TargetEndPoint |
where-object {$_.TargetEndPoint -ne "direct:execmgr"}
Returns the following, pay attention to the values which are similar to 00000000-0000-0000-0000-000000000116
ScheduledMessageID TargetEndPoint
------------------ --------------
{F83F662D-3DE6-4696-B064-701B2D86DADA} direct:UpdatesDeploymentAgent
{00000000-0000-0000-0000-000000000116} direct:StateMessageManager

Resources