Listing Superseded Updates using powershell in WSUS 3 - powershell

I am trying to automate the process of managing WSUS reports. I managed to
I) report the updates that I approve to WSUS console.
II) run a cleanup Process for the superseeding
So the script I use to list approved updated updates is:
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updatescope.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved
$updatescope.FromArrivalDAte = [datetime]"10/08/2013"
$wsusgroup = $wsus.GetComputerTargetGroups() | Where {$_.Name -eq "PCM_WSUS_spec"}
$updatescope
$updatescope.gettype()
$updatescope.count
$updateScope.ApprovedComputerTargetGroups.add($wsusgroup)
$wsus.GetUpdates($updatescope) | Select KnowledgebaseArticles,Title
$Updates = $wsus.GetUpdates($updatescope) | Select KnowledgebaseArticles
What I really need is a function to list of the updates went superseded based on the aboce list; updates that got approved after the given date.
Any ideas?

To build a list of superseded updates will require you to know the current update. Superseded updates are maintained as a list of UpdateIDs associated with the current update. (The list of superseding updates is built by traversing that list backwards.)
In the actual package XML, it looks like this:
<sdp:SupersededPackages xmlns:sdp="http://schemas.microsoft.com/wsus/2005/04/CorporatePublishing/SoftwareDistributionPackage.xsd">
<sdp:PackageID>b87cc8c1-b03d-4548-aa53-f0138ec6e2a3</sdp:PackageID>
<sdp:PackageID>7d8fcd7b-49d1-440d-8140-d6c33ce2cb80</sdp:PackageID>
<sdp:PackageID>76f68136-436f-42a5-9029-560b23702416</sdp:PackageID>
<sdp:PackageID>64b8c9d0-ecbc-4711-90de-b190d2ee7ee1</sdp:PackageID>
<sdp:PackageID>6bd77c33-1b2d-450d-91c6-259e101e57bb</sdp:PackageID>
<sdp:PackageID>94642c22-d70e-45b8-a70f-c09b86e2c4f5</sdp:PackageID>
</sdp:SupersededPackages>
But I do not know how it's actually accessed via the API (if even possible), or where in the database schema to find it.

Well I achieved to get a list using the following :
'[reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$WSUS = [Microsoft.UpdateServices.Administration.AdminProxy]::GetUpdateServer('wsupdates',$false,80)
#creating the update scope. Different parameters can be used each time for different reports needed
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$updatescope.ApprovedStates = [Microsoft.UpdateServices.Administration.ApprovedStates]::LatestRevisionApproved
$updatescope.FromArrivalDAte = [datetime]"10/08/2013"
$wsusgroup = $wsus.GetComputerTargetGroups() | Where {$_.Name -eq "PCM_WSUS_spec"}
$updateScope.ApprovedComputerTargetGroups.add($wsusgroup)
$updatescope
$updates = $wsus.GetUpdates($updatescope) | Select Title,UpdateClassificationTitle,SupersededUpdates | Sort Title |Export-csv 'C:\WSUS\test_approved.csv' -notype
$updates = $wsus.GetUpdates($updatescope)
$updates
$updates | ForEach-Object {
$temp =$_
Write-Output ' temp is'
$temp
$temp | Select Title| Export-CSV 'C:\wsus\superseded.csv' -Append -Notype -Force
$SupersededOnes = $_.GetRelatedUpdates(([Microsoft.UpdateServices.Administration.UpdateRelationship]::UpdatesSupersededByThisUpdate)) |Select Title
Write-Output 'after finding out superseded'
$SupersededOnes
Start-Sleep 8
$SupersededOnes | Export-CSV 'C:\wsus\superseded.csv' -Append -Notype -Force
}'
My issue is it appends the superseded beneath the approved, I need them to the column besides the associating superseding update.

Related

Getting a list of WSUS updates for Clients with matching fields out of the AD (Powershelll)

like statted in the title, I want to get a list from the WSUS, where every clients is listed with their required updates, already installed updates, not applicable updates etc..
(Comparable to the message you can get from the WSUS, telling you which computer group need updates)
I already managed to write the necessary part to get the necessary update-count from the WSUS:
[Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer('XXX',$true,XXXX)
$computerscope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$wsus.GetSummariesPerComputerTarget($updatescope,$computerscope) |
Format-Table #{L='ComputerTarget';E=$wsus.GetComputerTarget([guid]$_.ComputerTargetId)).FullDomainName}},
#{L='NeededCount';E={($_.DownloadedCount + $_.NotInstalledCount)}},DownloadedCount,NotApplicableCount,NotInstalledCount,InstalledCount,FailedCount -Autosize
Now I want to add a column to which shows me the AD field 'Description' for each client.
[Reflection.Assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer('XXX',$true,XXXX)
$computerscope = New-Object Microsoft.UpdateServices.Administration.ComputerTargetScope
$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope
$adusers = Get-ADComputer -Filter * -SearchBase "OU=XXX,DC=XXX,DC=XXX" -Properties "Description","DNSHostName"
$wsus.GetSummariesPerComputerTarget($updatescope,$computerscope) |
Format-Table #{L='ComputerTarget';E={($wsus.GetComputerTarget([guid]$_.ComputerTargetId)).FullDomainName}},
#{L='Username';E={???}},
#{L='NeededCount';E={($_.DownloadedCount + $_.NotInstalledCount)}},DownloadedCount,NotApplicableCount,NotInstalledCount,InstalledCount,FailedCount -Autosize
Can somebody help me?

Turn PowerShell Script into Process while Saving Resources

I apologize if this has been answered before, I have not been able to find an adequate answer to my needs here.
I am new to PowerShell. I have picked it up because I have a task I wish to accomplish in Windows Server 2016. I want to trigger a PowerShell script after each security event. It will check the event IDs and if it is the one I am seeking, it will pull certain pieces of data from the event log and add it to a list. I had intended to use Task Scheduler to accomplish this, but I ran into difficulty accounting for multiple events of the same kind happening at the same time. Since I could not easily differentiate between those logs to ensure I captured all of them, I went in a different direction.
# event name
$Name = 'Security'
# get an instance
$Log = [System.Diagnostics.EventLog]$Name
# determine what to do when an event occurs
$Action = {
# get the original event entry that triggered the event
$Evlog = $event.SourceEventArgs.Entry
$eventID = $Evlog | Select -ExpandProperty InstanceID
# do something based on the event
if ($eventID -eq 4724 -or $eventID -eq 4723)
{
$timeWritten = $Evlog | Select -ExpandProperty TimeWritten
$targetUserName = $Evlog | Select -ExpandProperty Message
$subjectUserName = $Evlog | Select -ExpandProperty Message
$entrytype = $Evlog | Select -ExpandProperty EntryType
# pulls subject and target username from 4724 logs
if ($eventID -eq 4724)
{
$SubjectUserName1 = (($subjectUserName -split '\r?\n')[$lineNo - 8]).split()[-1]
$TargetUserName1 = ($targetUserName -split '\r?\n')[$lineNo - 2].split()[-1]
}
# pulls subject and target username from 4723 logs
if ($eventID -eq 4723)
{
$SubjectUserName1 = (($subjectUserName -split '\r?\n')[$lineNo - 5]).split()[-1]
$TargetUserName1 = ($targetUserName -split '\r?\n')[$lineNo - 11].split()[-1]
}
# searches for email connected to target username
if ($TargetUserName1 -eq "")
{
$TargetEmail1 = "No User Targeted"
}else {
try {$TargetEmail1 = Get-ADUser -Identity $TargetUserName1 -Properties * | Select *mail* | Select-Object -ExpandProperty "mail"}
catch{$TargetEmail1 = "User Not Found"}
}
# specifying items that are added to list file
$array = [pscustomobject]#{
Timewritten = $timeWritten
EventID = $eventID
SubjectUserName = $SubjectUserName1
TargetUserName = $TargetUserName1
TargetEmail = $TargetEmail1
Result = $entrytype -replace ".{5}$"
}
# adding to list file
$array | Export-Csv E:\apps\me\testScript.csv -Append -NoTypeInformation
# clear memory
Get-Event | Remove-Event
}
}
# subscribe to its "EntryWritten" event
Register-ObjectEvent -InputObject $log -EventName EntryWritten -SourceIdentifier 'NewEventHandler' -Action $Action
I want to turn the script into a process that is always running, waiting for the events I am looking for and following the rest of the script once one appears. From what I understand, this script can directly pull the event that triggers it, ensuring that each event is accounted for.
When I have the ISE open and run the script, is seems to work as I want it to, but once I close the ISE, the process stops. I want this to be looking for events from startup of the server, and continue indefinitely. In a perfect world, I do not have to download and install a program that helps me do this, but I understand sometimes you simply must. (I have heard of NSSM, is it reliable?) I also want to make sure I do not waste more resources than I must to do this job.
How should I approach this problem?

Get the Last Logon Time for Each User Profile

I need to get the last logon time for each user profile on a remote computer (not the local accounts). I've tried something similar to the following using Win32_UserProfile & LastLogonTime, however this is not giving accurate results. For example, one this computer, only 1 account has been used in the past year, however LastUpdateTime is showing very recent dates. Some accounts have not even been logged into and should say "N/A", but it doesn't.
$RemoteSB_UserADID = Get-WmiObject win32_userprofile -Property * | Where-Object {$_.LocalPath -like "*users*"} | Sort-Object $_.LastUseTime | ForEach-Object{
$Parts = $_.LocalPath.Split("\")
$ADID = $Parts[$Parts.Length - 1]
if ($ADID -ne "SPECIALPURPOSEACCOUNT1" -and $ADID -ne "SPECIALPURPOSEACCOUNT2"){
$Time = $null
try{
$Time = $_.ConvertToDateTime($_.LastUseTime)
}catch{
$Time = "N/A"
}
"[$ADID | $Time]"
}
}
Example Output
[Acct1 | 03/13/2022 07:18:19]
[Acct2 | 03/15/202214:59:16]
[Acct3 | 03/13/2022 07:18:19]
[Acct4 | 03/16/2022 11:53:17] <--- only "active" account
How can I go about retrieving accurate (or decently accurate) login times for each user profile? Thanks!
It would help to know for what reason you need that, so that I know how to find a (better) solution for you.
If you need to cleanup your profiles not used for a long time at the target system, then take the last changed date of "ntuser.dat". That is the last logon if you define logon like logging on to a new session. If the user was logged on and simply locked the computer or used standby and then relogs then this date won't change.
Use this to get this date from all users you have access to but possibly not getting real user names
Get-ChildItem \\REMOTECOMPUTERNAMEHERE\Users\*\ntuser.dat -Attributes Hidden,Archive | Select #{Name="NameByFolder";Expression={($_.DirectoryName -split "\\")[-1]}},LastWriteTime
Or this a bit more complex version
Invoke-Command -ComputerName REMOTECOMPUTERNAMEHERE -ScriptBlock {$UsersWithProfilePath = #{}
dir "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" |
where {$_.name -like "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\S-1-5-21-*"} |
foreach {$UsersWithProfilePath[([System.Security.Principal.SecurityIdentifier]$_.name.split("\")[-1]).Translate( [System.Security.Principal.NTAccount]).Value] = $_.GetValue("ProfileImagePath")}
foreach ($Name in $UsersWithProfilePath.Keys) {#{$Name =(dir (join-path $UsersWithProfilePath.$Name ntuser.dat) -Attributes Hidden,Archive,System).LastWriteTime}}}
Depending on what you need you need to change it a bit.
Sorry for the long codelines... it is late here.

Office 365 Powershell - Formatting the output of a foreach-object loop

Good evening all,
Still pretty new to Powershell but I've been trying to get some scripts together to make some of the reporting easier for a lot of the information that we need to pull from our clients' Office 365 tenants.
The script already does what I need it to (grab the subscriptions from the tenant, grab the friendly name of the subscription from my CSV, then write to the console), however my question is about the formatting: I would like to format this output in a table with the column headers Subscriptions, Active, Suspended, Assigned.
$sku = Get-MsolAccountSku | select-object skupartnumber,ActiveUnits,suspendedUnits,ConsumedUnits | sort-object -property skupartnumber
$skudata = import-csv -Header friendlyname,skupartnumber "C:\PShell\SKUs.csv" | where-object {$sku.skupartnumber -eq $_.skupartnumber} | sort-object -property skupartnumber
$skudata | foreach-object {$n = 0}{write-host $skudata.friendlyname[$n], $sku.ActiveUnits[$n], $sku.SuspendedUnits[$n], $sku.ConsumedUnits[$n]; $n = $n + 1}
## Output:
POWER BI (FREE) 1000000 0 1
MICROSOFT TEAMS EXPLORATORY 100 0 6
Here's the script and output, I'm using write-host right now because this formats the data in the same way that we already want it and I can easily copy it out of the console and in to our tickets. When I use write-output, however, it puts each cell on its own line and I can't seem to figure out how to pipe this in to format-table .
$skudata | foreach-object {$n = 0}{write-output $skudata.friendlyname[$n], $sku.ActiveUnits[$n], $sku.SuspendedUnits[$n], $sku.ConsumedUnits[$n]; $n = $n + 1}
# Output:
POWER BI (FREE)
1000000
0
1
MICROSOFT TEAMS EXPLORATORY
100
0
6
I'm already able to get everything I need using the below hash table and simple script, the only problem is that it can't pull the common subscription name and Microsoft's skupartnumber identifier is the best option which isn't always very descriptive. I'm sure there's some very simple solution I'm just missing, but if anyone could point me in the right direction for either solution I've tried it would be greatly appreciated!
$subs = #{Label="Subscription"; Expression={$_.skupartnumber}; Alignment = "right";}
$active = #{Label="Active"; Expression={$_.ActiveUnits}; Alignment = "right"}
$assigned = #{Label="Assigned"; Expression={$_.ConsumedUnits}; Alignment = "right"}
$suspended = #{Label="Suspended"; Expression={$_.SuspendedUnits}; Alignment = "right"}
Get-MsolAccountSku | Format-Table $subs, $active, $suspended, $assigned -autosize
# Output:
Subscription Active Suspended Assigned
------------ ------ --------- --------
POWER_BI_STANDARD 1000000 0 1
TEAMS_EXPLORATORY 100 0 6
PowerShell is an object oriented language and what you need is to gather the info you want in objects for further processing or output.
Try
$result = for ($n = 0; $n -lt $skudata.Count; $n++) {
# output the data as PSObject that gets collected in variable $result
[PsCustomObject]#{
Subscription = $skudata.friendlyname[$n]
Active = $sku.ActiveUnits[$n]
Suspended = $sku.SuspendedUnits[$n]
Assigned = $sku.ConsumedUnits[$n]
}
}
# output on screen
$result | Format-Table -AutoSize
# output to CSV
$result | Export-Csv -Path "C:\PShell\SkuUsage.csv" -NoTypeInformation

PowerShell script to export to single CSV

I need help to get the following PowerShell script to output too just one .CSV file instead of the current two. Can someone please help? I'm wanting to get the SNMP settings that are set on remote servers. The information I'm after is held in the registry. $DellAdmKey is one key that holds SubKeys and refers to the "communityNames". Within each of the SubKeys I get the "Value Data" which refers to the "TrapDestinations". $DellAdmKey2 is a Key called "ValidCommunities" but does not have Subkeys, it just has DWORD values that refer to "AcceptedCommunityNames" and the "Rights". So the foreach on the 1st line can't be used as "ValidCommunities" does not contain SubKeys
$servers = "ServerName"
$BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(‘LocalMachine’, $server)
$SubKey= $BaseKey.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\SNMP\Parameters",$true)
$DellAdmKey = $BaseKey.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\SNMP\\Parameters\\TrapConfiguration\\",$true)
$DellAdmKey2 = $BaseKey.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\SNMP\\Parameters\\ValidCommunities\\",$true)
$DellAdmKey.GetSubKeyNames() | foreach {
$action = $_
$subkey = $DellAdmKey.openSubKey($_)
$subkey.GetValueNames() | foreach {$_ | Select
#{name="ServerName";Expression={$server}},
#{Name="CommunityNames";Expression={$action}},
#{name="TrapDestinations";Expression={$subkey.getvalue($_)}}
} | Export-Csv c:\temp\1.csv -NoTypeInformation
}
$action = $_
$subkey = $DellAdmKey2.openSubKey($_)
$subkey.GetValueNames() | foreach {
$_ | Select #{name="ServerName";Expression={$server}},
#{name="AcceptedCommunityNames";Expression={$_}},
#{name="Rights";Expression={$subkey.getvalue($_)}}
} | Export-Csv c:\temp\2.csv -NoTypeInformation
If I understand your question (and your code) correctly, the third group of instructions is supposed to go inside the $DellAdmKey.GetSubKeyNames() | foreach { ... }. In that case you simply need to move the | Export-Csv outside that loop to capture all output in one CSV. You need to make sure all objects have the same set of properties, though.
$DellAdmKey.GetSubKeyNames() | foreach {
$action = $_
$subkey = $DellAdmKey.openSubKey($action)
$subkey.GetValueNames() | Select #{name="ServerName";Expression={$server}},
#{Name="CommunityNames";Expression={$action}},
#{name="TrapDestinations";Expression={$subkey.getvalue($_)}}
#{name="AcceptedCommunityNames";Expression={}},
#{name="Rights";Expression={}}
$subkey = $DellAdmKey2.openSubKey($action)
$subkey.GetValueNames() | Select #{name="ServerName";Expression={$server}},
#{Name="CommunityNames";Expression={}},
#{name="TrapDestinations";Expression={}},
#{name="AcceptedCommunityNames";Expression={$_}},
#{name="Rights";Expression={$subkey.getvalue($_)}}
} | Export-Csv 'c:\temp\out.csv' -NoTypeInformation
Edit: If you really just want to run the code as you posted (which seems somewhat dubious to me, since $_ in line 17 should be empty), you could change the second Export-Csv instruction to
... | Export-Csv 'c:\temp\1.csv' -Append -NoTypeInformation
provided you're running PowerShell v3. Prior to that something like this should do:
... | ConvertTo-Csv -NoTypeInformation | select -Skip 1 |
Out-File 'c:\temp\1.csv' -Append
Either way you should make sure all the objects you're exporting have the same set of properties, though. Otherwise you might end up with Rights values in the TrapDestination column or some such.
I have resorted to using this http://psrr.codeplex.com/ it make life easier. Thanks for the replies Ansgar.