Viewing Print Queue Statistics with PowerShell

Leveraging WMI, you can easily view and manipulate print queues on a print server via PowerShell.  You can do anything from building a Print Queue, deleting a print queue and cancelling all print jobs on an existing queue. You can also use a specific WMI class to view all print jobs that have been processed since the last reboot of a print server or the last service reset of the Print Spooler service on the print server.

While I will not go into how to build a print queue or set up a printer using PowerShell, these links here will show you some great examples on performing this. Instead, my focus in this blog post will show you how easily it is to view the statistics of each print queue on a print server or locally connected printer.

Print Server Statistics

Using the Win32_PerfFormattedData_Spooler_PrintQueue class, you can easily see what printer queues are being used and which ones are not.  The statistics reset themselves when the Print Spooler service is restarted. So if a print server is rebooted, all of the data is reset.  One thing I found while playing with this is that if the printer is not connected to the server or if the printer is turned off, then the statistics will not be reset even if the spooler service has been restarted.  The only way for the statistics to be reset is to power on the printer or reconnect it to the server and perform the spooler reset. Using the following one-liner, you can see which queues have been used along with any that might have had an error at some point in time.

Get-WMIObject Win32_PerfFormattedData_Spooler_PrintQueue |
 Select Name, @{Expression={$_.jobs};Label="CurrentJobs"}, TotalJobsPrinted, JobErrors

Untitled

As it shows, I have one job currently being processed and two jobs total have been sent through the print spooler.  Keep in mind that this is done from my laptop, so running this against a print server hosting many more printers will be more impressive.

Using this on a print server would be great to find old print queues that may remain even after a printer has long since been decommissioned or to see what printers are seeing the most usage.

Putting this into a excel spreadsheet is as simple as piping it into an Export-CSV command. You could even set up a scheduled job that checks each print server once a week and email the results to an individual or group for analysis.

Posted in powershell | Tagged , , | 9 Comments

WSUS Administrator Module

This has been a few months in the making to get what I think is a decent build of my WSUS Administrator Module.  My goal with this module was to make something that provided enough use for most aspects of WSUS administration.  With that, I have put together 40 advanced functions that provide access to various parts of WSUS.  Some of these are commands to query different parts of wsus, such as clients, updates, server configurations, etc…  Other parts allow for making configuration changes or approving/disapproving updates, creating or removing Target groups and adding/removing clients from Target groups.  Some of the commands, such as ones that approve/decline updates, adds/removes a group, etc… have the whatif and confirm capabilities so you can verify that everything will happen as intended.

I have spent the last week testing it and working out all of the bugs, but most likely there are still some quirks here and there.  I would appreciate any comments, both positive and negative as anything said will help to make the next version of this module better.  I already have a list of probably 4 or 5 things that I want to add to this module for the next version.

So without further ado, I will walk through a few of the basic things in my module…

Importing the module

IMPORTANT!! You must have the WSUS Administrator Console installed on whichever computer that you will be running this function from. Download available here.

Be sure to save the module off as a .psm1 file so you can run the Import-Module cmdlet. Once you import the module, it will display a message telling you a couple of things that you can do to get started. I modeled this after the PowerCLI module as it seemed like a pretty good idea to show a couple of key things such as finding all of the commands available and how to make the initial connection to the server.

Untitled

Available Commands

As I said before, I currently have 40 available advanced functions to perform a variety of tasks in WSUS. Below is a list of the commands:

Add-WSUSClientToGroup
Approve-WSUSUpdate
Connect-WSUSServer
Convert-WSUSTargetGroup
Deny-WSUSUpdate
Disconnect-WSUSServer
Get-WSUSChildServers
Get-WSUSClient
Get-WSUSClientGroupMembership
Get-WSUSClients
Get-WSUSClientsInGroup
Get-WSUSCommands
Get-WSUSContentDownloadProgress
Get-WSUSDatabaseConfig
Get-WSUSDownstreamServers
Get-WSUSEmailConfig
Get-WSUSEvents
Get-WSUSGroup
Get-WSUSGroups
Get-WSUSServer
Get-WSUSStatus
Get-WSUSSubscription
Get-WSUSSyncHistory
Get-WSUSSyncProgress
Get-WSUSUpdate
Get-WSUSUpdateCategories
Get-WSUSUpdates
New-WSUSGroup
Remove-WSUSClient
Remove-WSUSClientFromGroup
Remove-WSUSGroup
Remove-WSUSUpdate
Resume-WSUSDownloads
Resume-WSUSUpdateDownload
Set-WSUSEmailConfig
Start-WSUSCleanup
Start-WSUSSync
Stop-WSUSDownloads
Stop-WSUSSync
Stop-WSUSUpdateDownload

Making Initial Connection to WSUS Server

To make the initial connection to the WSUS server, use the Connect-WSUSServer function. This will allow you to use the other advanced functions that are available within this module.

Untitled

Now that the connection has been made, we can begin looking at a couple of commands that will probably be getting a fair amount of use.

Viewing Updates

The Get-WSUSUpdates advanced function will grab all of the updates, so be careful using it as it may take a while to grab every update. Using the following command, I can pull the first 5 updates.

Get-WSUSUpdates | Select -first 5 | Select Title, KnowledgeBaseArticles, UpdateClassificationTitle

Untitled

If you want to look for a specific update, you can use Get-WSUSUpdate to accomplish this. This works much faster and is more helpful for filtering for specific updates.

Get-WSUSUpdate -Update "819639"

Untitled

WSUS Target Groups

You can view the current WSUS Target groups using the Get-WSUSGroups function.

Untitled

Creating a new WSUS Target group is as simple as using the New-WSUSGroup command.

New-WSUSGroup -Group "NewWSUSGroup"

Untitled

And removing a group using Remove-WSUSGroup.

Remove-WSUSGroup -Name "NewWSUSGroup"

Untitled

Approving and Declining WSUS Updates

There are a couple of ways to both approve and decline updates within this module.  The first way is supplying a string value and have the command search for and approve/decline the updates.

For approving and update, you have a few other things that are required in order make the approval successful.  The first is to supply a group that the update will be approved for. Second, you must provide an Action type  for the update approval, typically “Install”.  The third and not a required option is to provide a deadline for the update.

Approve-WSUSUpdate -Group "Domain Servers" -Update "819639" -Action Install

Untitled

Declining is much easier and only requires the update string to be entered.

Deny-WSUSUpdate  -Update "819639"

Untitled

Besides using the string method, both the Approve-WSUSUpdate and Deny-WSUSUpdate accepts input via the pipeline and allows you to use objects as well.  So combining the Get-WSUSUpdate or Get-WSUSUpdates with the approving or declining of updates will work just as well.

Get-WSUSUpdate -Update "819639" | Approve-WSUSUpdate -Group "Domain Servers" -Action Install

Untitled

Get-WSUSUpdate -Update "819639" | Deny-WSUSUpdate

Untitled

WSUS Synchronization

I have a few advanced functions that work with synchronization, such as pulling synchronization events with Get-WSUSSyncHistory, starting a manual synchronization with Start-WSUSSync and finding the current status of a synchronization using Get-WSUSSyncProgress.

Get-WSUSSyncHistory | select -first 1

Untitled

Start-WSUSSync

Untitled

Get-WSUSSyncProgress

Untitled

There is a parameter switch with Get-WSUSSyncProgress and Start-WSUSSync called ‘monitor’ which starts a background job that will alert you with a popup message stating that the synchronization has been completed.

This module is available for download from both the Script Repository and at Poshcode along with the code being available below.

Script Repository

Poshcode

Currently, the code I submitted to PoshCode appears to be getting truncated.  I left some feedback asking about this and am waiting on a reply. I will try again to re-post the code and see if it works, and if so, I will remove this message.

21NOV2010: Updated code for Connect-WSUSServer to allow for adding what port you want to connect to on the WSUS Server in case the server was configured for a port other than the default ports.

Write-Host "`n"
Write-Host "`t`tWSUS Administrator Module 1.0"
Write-Host "`n"
Write-Host -nonewline "Make initial connection to WSUS Server:`t"
Write-Host -fore Yellow "Connect-WSUSServer"
Write-Host -nonewline "Disconnect from WSUS Server:`t`t"
Write-Host -fore Yellow "Disconnect-WSUSServer"
Write-Host -nonewline "List all available commands:`t`t"
Write-Host -fore Yellow "Get-WSUSCommands"
Write-Host "`n"

function Get-WSUSCommands {
<#  
.SYNOPSIS  
    Lists all WSUS functions available from this module.
.DESCRIPTION
    Lists all WSUS functions available from this module.    
.NOTES  
    Name: Get-WSUSCommand
    Author: Boe Prox
    DateCreated: 18Oct2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Get-WSUSCommands 

Description
-----------
This command lists all of the available WSUS commands in the module.      
#> 
[cmdletbinding()]  
Param () 

#List all WSUS functions available
Get-Command *WSUS*  -CommandType Function  | Sort-Object Name
}

function Connect-WSUSServer {
<#  
.SYNOPSIS  
    Retrieves the last check-in times of clients on WSUS.
.DESCRIPTION
    Retrieves the last check-in times of clients on WSUS. You will need to run this on a machine that
    has the WSUS Administrator console installed.
.PARAMETER WsusServer
    Name of WSUS server to query against.          
.PARAMETER Secure
    Determines if a secure connection will be used to connect to the WSUS server. If not used, then a non-secure
    connection will be used.    
.PARAMETER Port
    Port number if default ports 80 and 443 not used    
.NOTES  
    Name: Get-LastCheckIn
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Connect-WSUSServer -wsusserver "server1"

Description
-----------
This command will make the connection to the WSUS using an unsecure port (Default:80).
.EXAMPLE
Connect-WSUSServer -wsusserver "server1"  -secure 

Description
-----------
This command will make a secure connection (Default: 443) to a WSUS server.   
.EXAMPLE
Connect-WSUSServer -wsusserver "server1" -port 8560

Description
-----------
This command will make the connection to the WSUS using a defined port 8560.  
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = '',
            ValueFromPipeline = $True)]
            [string]$WsusServer,                     
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = '',
            ValueFromPipeline = $False)]
            [switch]$Secure,   
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'port',
            ValueFromPipeline = $False)]
            [int]$port                                
            )            
#Load required assemblies            
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
#Make connection to WSUS server 
Write-Host -ForegroundColor Yellow "Attempting connection to WSUS Server: $($wsusserver)"   
$ErrorActionPreference = 'stop'
Try {
    If ($secure) {
        Switch ($pscmdlet.ParameterSetName) {
            "wsus" {
                $Global:wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$True)
                $Wsus | Select Name, Version,PortNumber
                }
            "port" {
                $Global:wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$True,$port)
                $Wsus | Select Name, Version,PortNumber                
                }                
            }
        }
    Else {
        Switch ($pscmdlet.ParameterSetName) {
            "wsus" {
                $Global:wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$False)
                $Wsus | Select Name, Version,PortNumber
                }
            "port" {
                $Global:wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$False,$port)
                $Wsus | Select Name, Version,PortNumber                
                }                
            }
        }
    }
Catch {
    Write-Error "Unable to connect to $($wsusserver)!`n$($error[0])"
    }                
}

function Disconnect-WSUSServer {
<#  
.SYNOPSIS  
    Disconnects session against WSUS server.
.DESCRIPTION
    Disconnects session against WSUS server.
.NOTES  
    Name: Disconnect-WSUSServer
    Author: Boe Prox
    DateCreated: 27Oct2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Disconnect-WSUSServer

Description
-----------
This command will disconnect the session to the WSUS server.  
       
#> 
[cmdletbinding()]  
Param () 
#Disconnect WSUS session by removing the variable   
Remove-Variable -Name wsus -Force
}

function Get-WSUSClients {
<#  
.SYNOPSIS  
    Retrieves a list of all of the clients in WSUS.
.DESCRIPTION
    Retrieves a list of all of the clients in WSUS.  
.NOTES  
    Name: Get-WSUSClients
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSClients

Description
-----------
This command will list every client in WSUS.  
       
#> 
[cmdletbinding()]  
Param () 
#Gather all computers in WSUS    
$wsus.GetComputerTargets()
}
        

function Start-WSUSSync {
<#  
.SYNOPSIS  
    Start synchronization on WSUS server.
.DESCRIPTION
    Start synchronization on WSUS server.
.PARAMETER Monitor
    Starts a synchronization and runs a background job to monitor currently running content download and 
    notifies user when completed.         
.NOTES  
    Name: Start-WSUSSync
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Start-WSUSSync

Description
-----------
This command will begin a manual sychronization on WSUS with the defined update source. 
.EXAMPLE
Start-WSUSSync -monitor

Description
-----------
This command will begin a manual synchronization on WSUS and will begin a background job that will notifiy via
pop-up message when the synchronization has completed.      
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'monitor',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)] 
Param (
    [Parameter(
        Mandatory = $False,
        Position = 0,
        ParameterSetName = 'monitor',
        ValueFromPipeline = $False)]
        [switch]$Monitor
    )
$sub = $wsus.GetSubscription()    
$sync = $sub.GetSynchronizationProgress()    
If ($monitor) {
    #Stop and remove any jobs for SyncMonitoring
    $jobs = Get-Job | ? {$_.Name -eq "WSUSSyncProgressMonitor"}
    If ($jobs) {
        $jobs | Stop-Job
        $jobs | Remove-Job
        }
    #Start WSUS synchronization
    If ($pscmdlet.ShouldProcess($($wsus.name))) {
            $sub.StartSynchronization()  
            "Synchronization have been started."
            Start-Sleep -Seconds 3
        Start-Job -Name "WSUSSyncProgressMonitor" -ArgumentList $sync -ScriptBlock {
            Param (
                $sync
                )
            #Load required assemblies for message window    
            [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)                        
            While ($sync.Phase -ne "NotProcessing") {
                $null
                }
            [System.Windows.Forms.MessageBox]::Show("Synchronization has been completed on WSUS",”Information”)            
            } | Out-Null
        }             
    }
Else {
    #Start WSUS synchronization
    If ($pscmdlet.ShouldProcess($($wsus.name))) {
        $sub.StartSynchronization()  
        "Synchronization have been started."
        } 
    } 
}         

function Stop-WSUSSync {
<#  
.SYNOPSIS  
    Stops a currently running WSUS sync.
.DESCRIPTION
    Stops a currently running WSUS sync.
.NOTES  
    Name: Stop-WSUSSync
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Stop-WSUSSync  

Description
-----------
This command will stop a currently running WSUS synchronization.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param()
$sub = $wsus.GetSubscription()      
#Cancel synchronization running on WSUS       
If ($pscmdlet.ShouldProcess($($wsus.name))) {
    $sub.StopSynchronization() 
    "Synchronization have been cancelled."
    }    
}        

function Get-WSUSSyncHistory {
<#  
.SYNOPSIS  
    Retrieves the synchronization history of the WSUS server.
.DESCRIPTION
    Retrieves the synchronization history of the WSUS server.    
.NOTES  
    Name: Get-WSUSSyncHistory 
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSSyncHistory

Description
-----------
This command will list out the entire synchronization history of the WSUS server.  
       
#> 
[cmdletbinding()]  
Param () 

$sub = $wsus.GetSubscription()
$sub.GetSynchronizationHistory()      
}  

function Get-WSUSSyncProgress {
<#  
.SYNOPSIS  
    Displays the current progress of a WSUS synchronization.
.DESCRIPTION
    Displays the current progress of a WSUS synchronization. 
.PARAMETER Monitor
    Runs a background job to monitor currently running synchonization and notifies user when completed.      
.NOTES  
    Name: Get-WSUSSyncProgress
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSSyncProgress 

Description
-----------
This command will show you the current status of the WSUS sync.
.EXAMPLE
Get-WSUSSyncProgress -monitor     

Description
-----------
This command will begin a background job that will notify you when the WSUS synchronization
has been completed.
       
#> 
[cmdletbinding()]  
Param (
    [Parameter(
        Mandatory = $False,
        Position = 0,
        ParameterSetName = 'monitor',
        ValueFromPipeline = $False)]
        [switch]$Monitor
    )
$sub = $wsus.GetSubscription()    
If ($monitor) {
    $job = Get-Job
    If ($job) {
        $job = Get-Job -Name "WSUSSyncProgressMonitor"
        }
    If ($job) {
        Get-Job -Name "WSUSSyncProgressMonitor" | Stop-Job
        Get-Job -Name "WSUSSyncProgressMonitor" | Remove-Job
        }    
    Start-Job -Name "WSUSSyncProgressMonitor" -ArgumentList $sub -ScriptBlock {
        Param (
            $sub
            )
        #Load required assemblies for message window    
        [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)                        
        While (($sub.GetSynchronizationProgress()).Phase -ne "NotProcessing") {
            $null
            }
        [System.Windows.Forms.MessageBox]::Show("Synchronization has been completed on WSUS",”Information”)            
        } | Out-Null 
    }
Else {
    #Gather all child servers in WSUS    
    $sub.GetSynchronizationProgress() 
    } 
     
}  

function Get-WSUSEvents {
<#  
.SYNOPSIS  
    Retrieves all WSUS events.
.DESCRIPTION
    Retrieves all WSUS events from the WSUS server.  
.NOTES  
    Name: Get-WSUSEvents
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSEvents  

Description
-----------
This command will show you all of the WSUS events.
       
#> 
[cmdletbinding()]  
Param () 

$sub = $wsus.GetSubscription()
$sub.GetEventHistory()      
}  

function Get-WSUSGroups {
<#  
.SYNOPSIS  
    Retrieves all of the WSUS Target Groups.
.DESCRIPTION
    Retrieves all of the WSUS Target Groups.    
.NOTES  
    Name: Get-WSUSGroups
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSGroups  

Description
-----------
This command will list out all of the WSUS Target groups and their respective IDs.
       
#> 
[cmdletbinding()]  
Param () 
 
$wsus.GetComputerTargetGroups()      
}  

function Get-WSUSServer {
<#  
.SYNOPSIS  
    Retrieves connection and configuration information from the WSUS server.
.DESCRIPTION
    Retrieves connection and configuration information from the WSUS server. 
.PARAMETER Configuration
    Lists more configuration information from WSUS Server      
.NOTES  
    Name: Get-WSUSServer
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSServer

Description
-----------
This command will display basic information regarding the WSUS server.
.EXAMPLE
Get-WSUSServer -configuration      

Description
-----------
This command will list out more detailed information regarding the configuration of the WSUS server.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(                         
        [Parameter(
            Mandatory = $False,
            Position = 0,
            ParameterSetName = 'wsus',
            ValueFromPipeline = $False)]
            [switch]$Configuration                     
            )                    
If ($configuration) {
    $wsus.GetConfiguration()
    }
Else {
    $wsus
    }        
}  

function Get-WSUSUpdates {
<#  
.SYNOPSIS  
    Retrieves all of the updates from a WSUS server.
.DESCRIPTION
    Retrieves all of the updates from a WSUS server.   
.NOTES  
    Name: Get-WSUSUpdates
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSUpdates  

Description
-----------
This command will list out every update that is in WSUS's database whether it has been approved or not.
       
#> 
[cmdletbinding()]  
Param () 
 
$wsus.GetUpdates()      
}  

function Get-WSUSEmailConfig {
<#  
.SYNOPSIS  
    Retrieves the email notification configuration from WSUS.
.DESCRIPTION
    Retrieves the email notification configuration from WSUS.
.PARAMETER SendTestEmail
    Optional switch that will send a test email to the configured email addresses        
.NOTES  
    Name: Get-WSUSEmailConfig
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
 Get-WSUSEmailConfig 
 
 Description
-----------
This command will display the configuration of the email notifications.
.EXAMPLE 
Get-WSUSEmailConfig -SendTestEmail    

Description
-----------
This command will send a test email to the address or addresses in the To field.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(                          
        [Parameter(
            Mandatory = $False,
            Position = 0,
            ParameterSetName = 'wsus',
            ValueFromPipeline = $False)]
            [switch]$SendTestEmail                   
            )                   
$email = $wsus.GetEmailNotificationConfiguration()    
If ($SendTestEmail) {
    $email.SendTestEmail()
    Write-Host -fore Green "Test email sent."
    }           
Else {
    $email
    }    
}  

function Get-WSUSUpdateCategories {
<#  
.SYNOPSIS  
    Retrieves the list of Update categories available from WSUS.
.DESCRIPTION
    Retrieves the list of Update categories available from WSUS.   
.NOTES  
    Name: Get-WSUSUpdateCategories
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSUpdateCategories  

Description
-----------
This command will list all of the categories for updates in WSUS.
       
#> 
[cmdletbinding()]  
Param () 
 
$wsus.GetUpdateCategories()      
}  

function Get-WSUSStatus {
<#  
.SYNOPSIS  
    Retrieves a list of all updates and their statuses along with computer statuses.
.DESCRIPTION
    Retrieves a list of all updates and their statuses along with computer statuses.   
.NOTES  
    Name: Get-WSUSStatus
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Get-WSUSStatus 

Description
-----------
This command will display the status of the WSUS server along with update statuses.
       
#> 
[cmdletbinding()]  
Param () 

$wsus.getstatus()      
}  

function Set-WSUSEmailConfig {
<#  
.SYNOPSIS  
    Configures the email notifications on a WSUS server.
.DESCRIPTION
    Configures the email notifications on a WSUS server. It is important to note that the email address to send
    the emails to is Read-Only and can only be configured from the WSUS Admin Console. After the settings have been
    changed, the new configuration will be displayed.
.PARAMETER EmailLanguage
    What type of language to send the email in.          
.PARAMETER SenderDisplayName
    The friendly name of where the email is coming from.    
.PARAMETER SenderEmailAddress
    The senders email address
.PARAMETER SendStatusNotification
    Determines if an email will be sent for a status notification    
.PARAMETER SendSyncnotification
    Determines if an email will be sent after a sync by WSUS
.PARAMETER SMTPHostname
    Server name of the smtp server to send email from
.PARAMETER SMTPPort
    Port number to be used to connect to smtp server to send email
.PARAMETER SmtpServerRequiresAuthentication
    Used if smtp server requires authentication
.PARAMETER SmtpUserName  
    Username to submit if required by smtp server
.PARAMETER StatusNotificationFrequency
    Frequency (Daily or Weekly) to send notifications
.PARAMETER StatusNotificationTimeOfDay
    Date/Time to send notifications
.PARAMETER UpdateServer
    Name of the WSUS update server
.PARAMETER SmtpPassword
    Password to user for smtp server connection.    
.NOTES  
    Name: Set-WSUSEmailConfig
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Set-WSUSEmailConfig -SenderDisplayName "WSUSAdmin" -SenderEmailAddress "wsusadmin@domain.com"

Description
-----------  
This command will change the sender name and email address for email notifications and then display the new settings.      
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $False, Position = 0,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string]$EmailLanguage,                     
        [Parameter(
            Mandatory = $False, Position = 1,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string]$SenderDisplayName,                          
        [Parameter(
            Mandatory = $False, Position = 2,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string]$SenderEmailAddress,   
        [Parameter(
            Mandatory = $False, Position = 3,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string][ValidateSet("True","False")]$SendStatusNotification,
        [Parameter(
            Mandatory = $False, Position = 4,
            ParameterSetName = '',ValueFromPipeline = $False)]
            [string][ValidateSet("True","False")]$SendSyncnotification,
        [Parameter(
            Mandatory = $False, Position = 5,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string]$SMTPHostname,
        [Parameter(
            Mandatory = $False, Position = 6,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [int]$SMTPPort,
        [Parameter(
            Mandatory = $False, Position = 7,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string][ValidateSet("True","False")]$SmtpServerRequiresAuthentication,    
        [Parameter(
            Mandatory = $False, Position = 8,
            ParameterSetName = 'account', ValueFromPipeline = $False)]
            [string]$SmtpUserName,
        [Parameter(
            Mandatory = $False, Position = 9,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string][ValidateSet("Daily","Weekly")]$StatusNotificationFrequency,
        [Parameter(
            Mandatory = $False, Position = 10,
            ParameterSetName = '', ValueFromPipeline = $False)]
            [string]$StatusNotificationTimeOfDay,
        [Parameter(
            Mandatory = $False,Position = 11,
            ParameterSetName = '',ValueFromPipeline = $False)]
            [string]$UpdateServer,
        [Parameter(
            Mandatory = $False,Position = 12,
            ParameterSetName = 'account',ValueFromPipeline = $False)]
            [string]$SmtpPassword                                                                                                                                                              
            )   
#Configure Email Notifications
$email = $wsus.GetEmailNotificationConfiguration()
$ErrorActionPreference = 'stop'
Try {
    If ($StatusNotificationTimeOfDay) {
        #Validate Notification Time of Day Parameter
        If (!([regex]::ismatch($StatusNotificationTimeOfDay,"^\d{2}:\d{2}$"))) {
            Write-Error "$($StatusNotificationTimeOfDay) is not a valid time to use!`nMust be 'NN:NN'"
            }
        Else {                
            $email.StatusNotificationTimeOfDay = $StatusNotificationTimeOfDay
            }
        }
    If ($UpdateServer) {$email.UpdateServer = $UpdateServer}
    If ($EmailLanguage) {$email.EmailLanguage = $EmailLanguage}
    If ($SenderDisplayName) {$email.SenderDisplayName = $SenderDisplayName}
    If ($SenderEmailAddress) {
        #Validate Email Address Parameter
        If (!([regex]::ismatch($SenderEmailAddress,"^\w+@\w+\.com|mil|org|net$"))) {
            Write-Error "$($SenderEmailAddress) is not a valid email address!`nMust be 'xxxx@xxxxx.xxx'"
            }
        Else {                    
            $email.SenderEmailAddress = $SenderEmailAddress
            }
        }
    If ($SMTPHostname) {$email.SMTPHostname = $SMTPHostname}
    If ($SMTPPort) {$email.SMTPPort = $SMTPPort}
    If ($SmtpServerRequiresAuthentication) {$email.SmtpServerRequiresAuthentication = $SmtpServerRequiresAuthentication}
    If ($SmtpUserName) {$email.SmtpUserName = $SmtpUserName}
    If ($SmtpPassword) {$mail.SetSmtpUserPassword($SmtpPassword)}
    Switch ($StatusNotificationFrequency) {
        "Daily" {$email.StatusNotificationFrequency = [Microsoft.UpdateServices.Administration.EmailStatusNotificationFrequency]::Daily}
        "Weekly" {$email.StatusNotificationFrequency = [Microsoft.UpdateServices.Administration.EmailStatusNotificationFrequency]::Weekly}
        Default {$Null}
        }
    Switch ($SendStatusNotification) {
        "True" {$email.SendStatusNotification = 1}
        "False" {$email.SendStatusNotification = 0}
        Default {$Null}
        }        
    Switch ($SendSyncNotification) {
        "True" {$email.SendSyncNotification = 1}
        "False" {$email.SendSyncNotification = 0}
        Default {$Null}
        } 
    }    
Catch {
    Write-Error "$($error[0])"
    }
#Save Configuration Changes
Try {
    $email.Save()
    Write-Host -fore Green "Email settings changed"
    $email
    }        
Catch {
    Write-Error "$($error[0])"
    }    
}  

function Convert-WSUSTargetGroup {
<#  
.SYNOPSIS  
    Converts the WSUS group from ID to Name or Name to ID.
.DESCRIPTION
    Converts the WSUS group from ID to Name or Name to ID.
.PARAMETER Id
    GUID of the group to be converted to friendly name          
.PARAMETER Name
    Name of the group to be converted to a guid    
.NOTES  
    Name: Convert-WSUSTargetGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Convert-WSUSTargetGroup -name "Domain Servers"

Description
-----------      
This command will convert the group name "Domain Servers" into the GUID format.

.EXAMPLE
Convert-WSUSTargetGroup -ID "b73ca6ed-5727-47f3-84de-015e03f6a88a"

Description
-----------    
This command will convert the given GUID into a friendly name.

#> 
[cmdletbinding(
	DefaultParameterSetName = 'name',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'id',
            ValueFromPipeline = $False)]
            [string]$Id,
        [Parameter(
            Mandatory = $False,
            Position = 3,
            ParameterSetName = 'name',
            ValueFromPipeline = $False)]
            [string]$Name                          
            )            
If ($name) {    
    Try {     
        $group = $wsus.GetComputerTargetGroups() | ? {$_.Name -eq $name}
        $group | Select -ExpandProperty ID
        }
    Catch {
        Write-Error "Unable to locate $($name)."
        }        
    }  
If ($id) {    
    Try {     
        $group = $wsus.GetComputerTargetGroups() | ? {$_.ID -eq $id}
        $group | Select -ExpandProperty Name
        }
    Catch {
        Write-Error "Unable to locate $($id)."
        }        
    }           
}  

function Get-WSUSClientsInGroup {
<#  
.SYNOPSIS  
    Retrieves a list of clients that are members of a group.
.DESCRIPTION
    Retrieves a list of clients that are members of a group.
.PARAMETER Id
    Retrieves list of clients in group by group guid.          
.PARAMETER Name
    Retrieves list of clients in group by group name.    
.NOTES  
    Name: Get-WSUSClientsInGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSClientsInGroup -name "Domain Servers"

Description
-----------      
This command will list all clients that are members of the specified group via group name.

.EXAMPLE
Get-WSUSClientsInGroup -ID "b73ca6ed-5727-47f3-84de-015e03f6a88a"

Description
-----------    
This command will list all clients that are members of the specified group via the group guid.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'name',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'name',            
            ValueFromPipeline = $False)]
            [string]$Name,
        [Parameter(
            Mandatory = $False,
            Position = 3,
            ParameterSetName = 'id',
            ValueFromPipeline = $False)]
            [string]$Id                           
            )                        
If ($id) {     
    ($wsus.GetComputerTargetGroups() | ? {$_.Id -eq $id}).GetComputerTargets()
    }
If ($name) {
    ($wsus.GetComputerTargetGroups() | ? {$_.name -eq $name}).GetComputerTargets()
    }    
}  

function Get-WSUSClient {
<#  
.SYNOPSIS  
    Retrieves information about a WSUS client.
.DESCRIPTION
    Retrieves information about a WSUS client.
.PARAMETER Computer
    Name of the client to search for. Accepts a partial name.
.NOTES  
    Name: Get-WSUSClient
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSClient -computer "server1"

Description
-----------      
This command will search for and display all computers matching the given input. 
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'wsus',
            ValueFromPipeline = $True)]
            [string]$Computer                                  
            )                
$ErrorActionPreference = 'stop'    
#Retrieve computer in WSUS
Try {      
    $wsus.SearchComputerTargets($computer)
    }
Catch {
    Write-Error "Unable to retrieve $($computer) from database."
    }    
}

function New-WSUSGroup {
<#  
.SYNOPSIS  
    Creates a new WSUS Target group.
.DESCRIPTION
    Creates a new WSUS Target group.
.PARAMETER Group
    Name of group being created.
.PARAMETER ParentGroupName
    Name of group being created.
.PARAMETER ParentGroupId
    Name of group being created.        
.NOTES  
    Name: New-WSUSGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
New-WSUSGroup -name "TestGroup"

Description
-----------  
This command will create a new Target group called 'TestGroup'       
.EXAMPLE 
New-WSUSGroup -name "TestGroup" -parentgroupname "Domain Servers"

Description
-----------  
This command will create a new Target group called 'TestGroup' under the parent group 'Domain Servers'    
#> 
[cmdletbinding(
	DefaultParameterSetName = 'group',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = '',
            ValueFromPipeline = $True)]
            [string]$Group,
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'parentgroup',
            ValueFromPipeline = $True)]
            [string]$ParentGroupName,
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'parentgroup',
            ValueFromPipeline = $True)]
            [string]$ParentGroupId                                              
            ) 
Process {
    #Determine action based on Parameter Set Name
    Switch ($pscmdlet.ParameterSetName) {            
        "group" {
            Write-Verbose "Creating computer group"        
            If ($pscmdlet.ShouldProcess($group)) {
                #Create the computer target group
                $wsus.CreateComputerTargetGroup($group)
                "$($group) has been created."
                }
            }                
        "parentgroup" {
            If ($parentgroupname) {
                #Retrieve group based off of name
                Write-Verbose "Querying for parent group"
                $parentgroup = Get-WSUSGroups | ? {$_.name -eq $parentgroupname}
                If (!$parentgroup) {
                    Write-Error "Parent Group name `'$parentgroupname`' does not exist in WSUS!"
                    Break
                    }
                }
            If ($parentgroupid) {
                #Retrieve group based off of guid
                Write-Verbose "Querying for parent group"
                $parentgroup = Get-WSUSGroups | ? {$_.id -eq $parentgroupid}
                If (!$parentgroup) {
                    Write-Error "Parent Group id `'$parentgroupid`' does not exist in WSUS!"
                    Break
                    }                
                }                    
            Write-Verbose "Creating computer group"                
            If ($pscmdlet.ShouldProcess($group)) {
                #Create the computer target group
                $wsus.CreateComputerTargetGroup($group,$parentgroup)
                "$($group) has been created under $($parentgroup.Name)."
                }            
            }                
        }
    }                
}            

function Get-WSUSUpdate {
<#  
.SYNOPSIS  
    Retrieves information from a wsus update.
.DESCRIPTION
    Retrieves information from a wsus update. Depending on how the information is presented in the search, more
    than one update may be returned. 
.PARAMETER Update
    String to search for. This can be any string for the update to include
    KB article numbers, name of update, category, etc... Use of wildcards (*,%) not allowed in search!
.NOTES  
    Name: Get-WSUSUpdate
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Get-WSUSUpdate -update "Exchange"

Description
-----------  
This command will list every update that has 'Exchange' in it.
.EXAMPLE
Get-WSUSUpdate -update "925474"

Description
-----------  
This command will list every update that has '925474' in it.
#> 
[cmdletbinding(
	DefaultParameterSetName = 'wsus',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'wsus',
            ValueFromPipeline = $True)]
            [string]$Update                                  
            )                
$ErrorActionPreference = 'stop'    
#Retrieve computer in WSUS
Try {      
    $wsus.SearchUpdates($update)
    }
Catch {
    Write-Error "Unable to retrieve $($update) from database."
    }    
}

function Remove-WSUSGroup {
<#  
.SYNOPSIS  
    Creates a new WSUS Target group.
.DESCRIPTION
    Creates a new WSUS Target group.
.PARAMETER Name
    Name of group being deleted.
.PARAMETER Id
    Id of group being deleted.       
.NOTES  
    Name: Remove-WSUSGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Remove-WSUSGroup  -name "Domain Servers"

Description
-----------  
This command will remove the Domain Servers WSUS Target group.  
.EXAMPLE 
Remove-WSUSGroup  -id "fc93e74e-ba59-4593-9ff7-690af1be695f"

Description
-----------  
This command will remove the Target group with ID 'fc93e74e-ba59-4593-9ff7-690af1be695f' from WSUS.       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'name',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $False,
            Position = 0,
            ParameterSetName = 'name',
            ValueFromPipeline = $True)]
            [string]$Name,
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'id',
            ValueFromPipeline = $True)]
            [string]$Id
        )            
Process {
    #Determine action based on Parameter Set Name
    Switch ($pscmdlet.ParameterSetName) {            
        "name" {
            Write-Verbose "Querying for computer group"
            $group = Get-WSUSGroup -name $name
            If (!$group) {
                Write-Error "Group $name does not exist in WSUS!"
                Break
                }
            Else {                               
                If ($pscmdlet.ShouldProcess($name)) {
                    #Create the computer target group
                    $group.Delete()
                    "$($name) has been deleted from WSUS."
                    }
                }                    
            }                
        "id" {
            Write-Verbose "Querying for computer group"
            $group = Get-WSUSGroup -id $id
            If (!$group) {
                Write-Error "Group $id does not exist in WSUS!"
                Break
                }                                       
            If ($pscmdlet.ShouldProcess($id)) {
                #Create the computer target group
                $group.Delete()
                "$($id) has been deleted from WSUS."
                }            
            }                
        }
    }                
} 

function Add-WSUSClientToGroup {
<#  
.SYNOPSIS  
    Adds a computer client to an existing WSUS group.
.DESCRIPTION
    Adds a computer client to an existing WSUS group.
.PARAMETER Group
    Name of group to add client to.
.PARAMETER Computer
    Name of computer being added to group.        
.NOTES  
    Name: Add-WSUSClientToGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Add-WSUSClientToGroup -group "Domain Servers" -computer "server1"

Description
-----------  
This command will add the client "server1" to the WSUS target group "Domain Servers".       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'group',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'group',
            ValueFromPipeline = $True)]
            [string]$Group,
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'group',
            ValueFromPipeline = $True)]
            [string]$Computer                                             
            )
#Verify Computer is in WSUS
Write-Verbose "Validating client in WSUS"
$client = Get-WSUSClient -computer $computer
If ($client) {
    #Get group object
    Write-Verbose "Retrieving group"
    $targetgroup = Get-WSUSGroup -name $group
    If (!$targetgroup) {
        Write-Error "Group $group does not exist in WSUS!"
        Break
        }    
    #Add client to group
    Write-Verbose "Adding client to group"
    If ($pscmdlet.ShouldProcess($($client.fulldomainname))) {
        $targetgroup.AddComputerTarget($client)
        "$($client.FullDomainName) has been added to $($group)"
        }
    }
Else {
    Write-Error "Computer: $computer is not in WSUS!"
    }    
}           

function Remove-WSUSClientFromGroup {
<#  
.SYNOPSIS  
    Removes a computer client to an existing WSUS group.
.DESCRIPTION
    Removes a computer client to an existing WSUS group.
.PARAMETER Group
    Name of group to remove client from.
.PARAMETER Computer
    Name of computer being removed from group.        
.NOTES  
    Name: Remove-WSUSClientToGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Remove-WSUSClientFromGroup -group "Domain Servers" -computer "server1"

Description
-----------  
This command will remove the client "server1" from the WSUS target group "Domain Servers".       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'group',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'group',
            ValueFromPipeline = $True)]
            [string]$Group,
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'group',
            ValueFromPipeline = $True)]
            [string]$Computer                                             
            )
#Verify Computer is in WSUS
$client = Get-WSUSClient -computer $computer
If ($client) {
    #Get group object
    Write-Verbose "Retrieving group"
    $targetgroup = Get-WSUSGroup -name $group
    If (!$targetgroup) {
        Write-Error "Group $group does not exist in WSUS!"
        Break
        }
    #Remove client from group
    Write-Verbose "Removing client to group"
    If ($pscmdlet.ShouldProcess($($client.fulldomainname))) {
        $targetgroup.RemoveComputerTarget($client)
        "$($client.fulldomainname) has been removed from $($group)"
        }
    }
Else {
    Write-Error "Computer: $computer is not in WSUS!"
    }    
}             

function Get-WSUSDatabaseConfig {
<#  
.SYNOPSIS  
    Displays the current WSUS database configuration.
.DESCRIPTION
    Displays the current WSUS database configuration.
.NOTES  
    Name: Get-WSUSDatabaseConfig
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Get-WSUSDatabaseConfig 

Description
-----------  
This command will display the configuration information for the WSUS connection to a database.       
#> 
[cmdletbinding()]  
Param () 

$wsus.GetDatabaseConfiguration()      
} 

function Get-WSUSSubscription {
<#  
.SYNOPSIS  
    Displays WSUS subscription information.
.DESCRIPTION
    Displays WSUS subscription information. You can view the next synchronization time, who last modified the schedule, etc...
.NOTES  
    Name: Get-WSUSSubscription
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSSubscription      

Description
-----------  
This command will list out the various subscription information on the WSUS server.
#> 
[cmdletbinding()]  
Param () 

$wsus.GetSubscription()     
} 

function Deny-WSUSUpdate {
<#  
.SYNOPSIS  
    Declines an update on WSUS.
.DESCRIPTION
    Declines an update on WSUS. Use of the -whatif is advised to be sure you are declining the right patch or patches.
.PARAMETER InputObject
    Collection of update/s being declined. This must be an object, otherwise it will fail.      
.PARAMETER Update
    Name of update/s being declined.    
.NOTES  
    Name: Deny-WSUSUpdate
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE
Get-WSUSUpdate -update "Exchange 2010" | Deny-WSUSUpdate 

Description
-----------  
This command will decline all updates with 'Exchange 2010' in its metadata.
.EXAMPLE
Deny-WSUSUpdate  -Update "Exchange 2010"

Description
-----------  
This command will decline all updates with 'Exchange 2010' in its metadata.
.EXAMPLE
$updates = Get-WSUSUpdate -update "Exchange 2010" 
Deny-WSUSUpdate -InputObject $updates

Description
-----------  
This command will decline all updates with 'Exchange 2010' in its metadata.   
.EXAMPLE
Get-WSUSUpdate -update "Exchange 2010" | Deny-WSUSUpdate

Description
-----------  
This command will decline all updates with 'Exchange 2010' in its metadata via the pipeline.    
#> 
[cmdletbinding(
	DefaultParameterSetName = 'collection',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'collection',
            ValueFromPipeline = $True)]
            [system.object]$InputObject,                                          
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'string',
            ValueFromPipeline = $False)]
            [string]$Update                                          
            )                       
Process {
    Switch ($pscmdlet.ParameterSetName) {
        "Collection" {
            Write-Verbose "Using 'Collection' set name"        
            #Change the collection to patches for use in loop
            $patches = $inputobject        
            }
        "String" {
            Write-Verbose "Using 'String' set name"        
            #Gather all updates from given information
            Write-Verbose "Searching for updates"
            $patches = Get-WSUSUpdate -update $update        
            }
        } 
    ForEach ($patch in $patches) {
        #Decline the update
        Write-Verbose "Declining update"                
        If ($pscmdlet.ShouldProcess($($patch.title))) {
            $patch.Decline($True) | out-null
            #Print out report of what was declined
            New-Object PSObject -Property @{
                Patch = $patch.title
                ApprovalAction = "Declined"
                }
            }         
        }
    }    
}            

function Approve-WSUSUpdate {
<#  
.SYNOPSIS  
    Approves a WSUS update for a specific group with an optional deadline.
.DESCRIPTION
    Approves a WSUS update for a specific group with an optional deadline.
.PARAMETER InputObject
    Update object that is being approved.    
.PARAMETER Update
    Name of update being approved.
.PARAMETER Group
    Name of group which will receive the update.       
.PARAMETER Deadline
    Optional deadline for client to install patch.
.PARAMETER Action
    Type of approval action to take on update. Accepted values are Install, Approve, Uninstall and NotApproved       
.NOTES  
    Name: Approve-WSUSUpdate
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Approve-WSUSUpdate -update "KB979906" -Group "Domain Servers" -Action Install

Description
----------- 
This command will approve all updates with the KnowledgeBase number of KB979906 for the 'Domain Servers' group and
the action command of 'Install'.
.EXAMPLE  
Approve-WSUSUpdate -update "KB979906" -Group "Domain Servers" -Action Install -Deadline (get-Date).AddDays(3)

Description
----------- 
This command will approve all updates with the KnowledgeBase number of KB979906 for the 'Domain Servers' group and
the action command of 'Install' and sets a deadline for 3 days from when this command is run.
.EXAMPLE  
Get-WSUSUpdate -Update "KB979906" | Approve-WSUSUpdate -Group "Domain Servers" -Action Install

Description
----------- 
This command will take the collection of objects from the Get-WSUSUpdate command and then approve all updates for 
the 'Domain Servers' group and the action command of 'Install'.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'string',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(            
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'collection',
            ValueFromPipeline = $True)]
            [system.object]
            [ValidateNotNullOrEmpty()]
            $InputObject,     
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'string',
            ValueFromPipeline = $False)]
            [string]$Update,           
        [Parameter(
            Mandatory = $True,
            Position = 1,
            ParameterSetName = '',
            ValueFromPipeline = $False)]
            [string]
            [ValidateSet("Install", "All", "NotApproved","Uninstall")]
            $Action,              
        [Parameter(
            Mandatory = $True,
            Position = 2,
            ParameterSetName = '',
            ValueFromPipeline = $False)]
            [string]$Group,
        [Parameter(
            Mandatory = $False,
            Position = 3,
            ParameterSetName = '',
            ValueFromPipeline = $False)]
            [datetime]$Deadline                      
        )
Begin {
    #Define the actions available
    Write-Verbose "Defining available approval actions"
    $Install = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install
    $All = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::All
    $NotApproved = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved
    $Uninstall = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Uninstall
    
    #Search for group specified
    Write-Verbose "Searching for group"        
    $targetgroup = Get-WSUSGroup -name $group
    If (!$targetgroup) {
        Write-Error "Group $group does not exist in WSUS!"
        Break
        }       
    }                    
Process {
    #Perform appropriate action based on Parameter set name
    Switch ($pscmdlet.ParameterSetName) {            
        "collection" {
            Write-Verbose "Using 'Collection' set name"
            #Change the variable that will hold the objects
            $patches = $inputobject    
            }                
        "string" {
            Write-Verbose "Using 'String' set name"
            #Search for updates
            Write-Verbose "Searching for update/s"
            $patches = Get-WSUSUpdate -update $update
            If (!$patches) {
                Write-Error "Update $update could not be found in WSUS!"
                Break
                }                     
            }                
        } 
    ForEach ($patch in $patches) {
        #Determine if Deadline is required
        If ($deadline) {
            Write-Verbose "Approving update with a deadline."
            If ($pscmdlet.ShouldProcess($($patch.title))) {
                #Create the computer target group
                $patch.Approve($action,$targetgroup,$deadline) | out-null
                #Print out report of what was approved
                New-Object PSObject -Property @{
                    Patch = $patch.title
                    TargetGroup = $group
                    ApprovalAction = $action
                    Deadline = "$($deadline)"
                    } 
                }        
            }
        Else {    
            #Approve the patch
            Write-Verbose "Approving update without a deadline."                              
            If ($pscmdlet.ShouldProcess($($patch.title))) {
                #Create the computer target group
                $patch.Approve($action,$targetgroup) | out-null
                #Print out report of what was approved
                New-Object PSObject -Property @{
                    Patch = $patch.title
                    TargetGroup = $group
                    ApprovalAction = $action
                    }               
                }
            }                 
        }
    }                
} 

function Get-WSUSGroup {
<#  
.SYNOPSIS  
    Retrieves specific WSUS target group.
.DESCRIPTION
    Retrieves specific WSUS target group.
.PARAMETER Name
    Name of group to search for. No wildcards allowed. 
.PARAMETER Id
    GUID of group to search for. No wildcards allowed.       
.NOTES  
    Name: Get-WSUSGroups
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSGroup -name "Domain Servers"

Description
----------- 
This command will search for the group and display the information for Domain Servers"

.EXAMPLE  
Get-WSUSGroup -ID "0b5ba818-021e-4238-8098-7245b0f90557"

Description
----------- 
This command will search for the group and display the information for the WSUS 
group guid 0b5ba818-021e-4238-8098-7245b0f90557"
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'name',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
    )]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'name',
            ValueFromPipeline = $False)]
            [string]$Name,
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'id',
            ValueFromPipeline = $False)]
            [string]$Id            
        )            
Switch ($pscmdlet.ParameterSetName) {
    "name" {$wsus.GetComputerTargetGroups() | ? {$_.name -eq $name}}
    "id"   {$wsus.GetComputerTargetGroups() | ? {$_.id -eq $id}}
    }
}  

function Remove-WSUSUpdate {
<#  
.SYNOPSIS  
    Removes an update on WSUS.
.DESCRIPTION
    Removes an update on WSUS. Use of the -whatif is advised to be sure you are declining the right patch or patches.
.PARAMETER Update
    Name of update being removed.       
.NOTES  
    Name: Remove-WSUSUpdate
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Remove-WSUSUpdate -update "KB986569"

Description
----------- 
This command will remove all instances of KB986569 from WSUS.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'update',
            ValueFromPipeline = $True)]
            [string]$Update                                          
            ) 
Begin {
    #Gather all updates from given information
    Write-Verbose "Searching for updates"
    $patches = Get-WSUSUpdate -update $update
    }            
Process {
    ForEach ($patch in $patches) {
        #Storing update guid
        $guid = ($patch.id).updateid              
        If ($pscmdlet.ShouldProcess($($patch.title))) {
            $wsus.DeleteUpdate($id,$True)
            "$($patch.title) has been deleted from WSUS"
            }         
        }
    }    
}   

function Stop-WSUSDownloads {
<#  
.SYNOPSIS  
    Cancels all current WSUS downloads.
.DESCRIPTION
    Cancels all current WSUS downloads.      
.NOTES  
    Name: Stop-WSUSDownloads
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Stop-WSUSDownloads

Description
-----------  
This command will stop all updates being downloaded to the WSUS server.
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param() 
#Cancel all downloads running on WSUS       
If ($pscmdlet.ShouldProcess($($wsus.name))) {
    $wsus.CancelAllDownloads()
    "Downloads have been cancelled."
    }
}    

function Resume-WSUSDownloads {
<#  
.SYNOPSIS  
    Resumes all current WSUS downloads.
.DESCRIPTION
    Resumes all current WSUS downloads that had been cancelled.
.NOTES  
    Name: Resume-WSUSDownloads
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE 
Resume-WSUSDownloads

Description
----------- 
This command will resume the downloading of updates to the WSUS server. 
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param() 
#Cancel all downloads running on WSUS       
If ($pscmdlet.ShouldProcess($($wsus.name))) {
    $wsus.ResumeAllDownloads()
    "Downloads have been resumed."
    }
} 

function Stop-WSUSUpdateDownload {
<#  
.SYNOPSIS  
    Stops update download after approval.
.DESCRIPTION
    Stops update download after approval.
.PARAMETER update
    Name of update to cancel download.       
.NOTES  
    Name: Stop-WSUSUpdateDownload
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Stop-WSUSUpdateDownload -update "KB965896"

Description
----------- 
This command will cancel the download of update KB956896.       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'update',
            ValueFromPipeline = $True)]
            [string]$update                                          
            ) 
Begin {
    #Gather all updates from given information
    Write-Verbose "Searching for updates"
    $patches = Get-WSUSUpdate -update $update
    }            
Process {
    If ($patches) {
        ForEach ($patch in $patches) {
            Write-Verbose "Cancelling update download"                
            If ($pscmdlet.ShouldProcess($($patch.title))) {
                $patch.CancelDownload()
                "$($patch.title) download has been cancelled."
                }         
            }
        }
    Else {
        Write-Warning "No patches found that need downloading cancelled."
        }        
    }    
} 

function Resume-WSUSUpdateDownload {
<#  
.SYNOPSIS  
    Resumes previously cancelled update download after approval.
.DESCRIPTION
    Resumes previously cancelled update download after approval.
.PARAMETER Update
    Name of cancelled update download to resume download.       
.NOTES  
    Name: Resume-WSUSUpdateDownload
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Resume-WSUSUpdateDownload -update "KB965896"

Description
----------- 
This command will resume the download of update KB956896 that was previously cancelled.       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'update',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'update',
            ValueFromPipeline = $True)]
            [string]$Update                                          
            ) 
Begin {
    #Gather all updates from given information
    Write-Verbose "Searching for updates"
    $patches = Get-WSUSUpdate -update $update
    }                
Process {
    If ($patches) {
        ForEach ($patch in $patches) {
            Write-Verbose "Resuming update download"                
            If ($pscmdlet.ShouldProcess($($patch.title))) {
                $patch.ResumeDownload()
                "$($patch.title) download has been resumed."
                }         
            }
        }
    Else {
        Write-Warning "No patches found needing to resume download!"
        }        
    }    
} 

function Start-WSUSCleanup {
<#  
.SYNOPSIS  
    Performs a cleanup on WSUS based on user inputs.
.DESCRIPTION
    Performs a cleanup on WSUS based on user inputs.
.PARAMETER DeclineSupersededUpdates
    Declined Superseded Updates will be removed.
.PARAMETER DeclineExpiredUpdates
    Expired updates should be declined.
.PARAMETER CleanupObsoleteUpdates
    Delete obsolete updates from the database.
.PARAMETER CompressUpdates
    Obsolete revisions to updates should be deleted from the database.
.PARAMETER CleanupObsoleteComputers
    Delete obsolete computers from the database.
.PARAMETER CleanupUnneededContentFiles 
    Delete unneeded update files.   
.NOTES  
    Name: Start-WSUSCleanup
    Author: Boe Prox
    DateCreated: 24SEPT2010            
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Start-WSUSCleanup -CompressUpdates -CleanupObsoleteComputers

Description
----------- 
This command will run the WSUS cleanup wizard and delete obsolete computers from the database and delete obsolete update 
revisions from the database.  
.EXAMPLE 
Start-WSUSCleanup -CompressUpdates -CleanupObsoleteComputers -DeclineExpiredUpdates -CleanupObsoleteUpdates -CleanupUnneededContentFiles 

Description
----------- 
This command performs a full WSUS cleanup against the database.      
#> 
[cmdletbinding(
	DefaultParameterSetName = 'cleanup',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True
)]
    Param(
        [Parameter(
            Mandatory = $False,
            Position = 0,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$DeclineSupersededUpdates,   
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$DeclineExpiredUpdates,  
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$CleanupObsoleteUpdates,  
        [Parameter(
            Mandatory = $False,
            Position = 3,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$CompressUpdates,  
        [Parameter(
            Mandatory = $False,
            Position = 4,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$CleanupObsoleteComputers,  
        [Parameter(
            Mandatory = $False,
            Position = 5,
            ParameterSetName = 'cleanup',
            ValueFromPipeline = $False)]
            [switch]$CleanupUnneededContentFiles                                                                                                     
            ) 
Begin {            
    #Create cleanup scope
    $cleanScope = new-object Microsoft.UpdateServices.Administration.CleanupScope
    #Create cleanup manager object
    $cleanup = $wsus.GetCleanupManager()

    #Determine what will be in the scope
    If ($DeclineSupersededUpdates) {
        $cleanScope.DeclineSupersededUpdates = $True
        }
    If ($DeclineExpiredUpdates) {
        $cleanScope.DeclineExpiredUpdates = $True
        }
    If ($CleanupObsoleteUpdates) {
        $cleanScope.CleanupObsoleteUpdates = $True
        }        
    If ($CompressUpdates) {
        $cleanScope.CompressUpdates = $True
        }
    If ($CleanupObsoleteComputers) {
        $cleanScope.CleanupObsoleteComputers = $True
        }
    If ($CleanupUnneededContentFiles) {
        $cleanScope.CleanupUnneededContentFiles = $True
        }
    }
Process {
    Write-Host "Beginning cleanup"                
    If ($pscmdlet.ShouldProcess($($wsus.name))) {
        $cleanup.PerformCleanup($cleanScope)
        }     
    }                        

}

function Get-WSUSChildServers {
<#  
.SYNOPSIS  
    Retrieves all WSUS child servers.
.DESCRIPTION
    Retrieves all WSUS child servers.
.NOTES  
    Name: Get-WSUSChildServers
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSChildServers

Description
----------- 
This command will display all of the Child WSUS servers.
       
#> 
[cmdletbinding()]  
Param () 
#Gather all child servers in WSUS    
$wsus.GetChildServers()
}

function Get-WSUSDownstreamServers {
<#  
.SYNOPSIS  
    Retrieves all WSUS downstream servers.
.DESCRIPTION
    Retrieves all WSUS downstream servers.
.NOTES  
    Name: Get-WSUSDownstreamServers
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSDownstreamServers

Description
----------- 
This command will display a list of all of the downstream WSUS servers.
       
#> 
[cmdletbinding()]  
Param () 
#Gather all child servers in WSUS    
$wsus.GetDownstreamServers()
}

function Get-WSUSContentDownloadProgress {
<#  
.SYNOPSIS  
    Retrieves the progress of currently downloading updates. Displayed in bytes downloaded.
.DESCRIPTION
    Retrieves the progress of currently downloading updates. Displayed in bytes downloaded.
.PARAMETER Monitor
    Runs a background job to monitor currently running content download and notifies user when completed.    
.NOTES  
    Name: Get-WSUSContentDownloadProgress
    Author: Boe Prox
    DateCreated: 24SEPT2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSContentDownloadProgress

Description
----------- 
This command will display the current progress of the content download.
.EXAMPLE  
Get-WSUSContentDownloadProgress -monitor

Description
----------- 
This command will create a background job that will monitor the progress and alert the user via pop-up message that
the download has been completed.
       
#> 
[cmdletbinding()]  
Param (
    [Parameter(
        Mandatory = $False,
        Position = 0,
        ParameterSetName = 'monitor',
        ValueFromPipeline = $False)]
        [switch]$Monitor
    )
If ($monitor) {
    #Stop and remove same jobs if existing
        $job = Get-Job
    If ($job) {
        $job = Get-Job -Name "WSUSSyncProgressMonitor"
        }
    If ($job) {
        Get-Job -Name "WSUSContentProgressMonitor" | Stop-Job
        Get-Job -Name "WSUSContentProgressMonitor" | Remove-Job
        }
    Start-Job -Name "WSUSContentProgressMonitor" -ArgumentList $wsus -ScriptBlock {
        Param (
            $wsus
            )
        #Load required assemblies for message window    
        [void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)            
        $progress = $wsus.GetContentDownloadProgress()            
        While ($progress.TotalBytesToDownload -ne $progress.DownloadedBytes) {
            $null
            }
        [System.Windows.Forms.MessageBox]::Show("Content download has been completed on WSUS",”Information”)            
        } | Out-Null 
    }
Else {
    #Gather all child servers in WSUS    
    $wsus.GetContentDownloadProgress()
    }         
}

function Remove-WSUSClient {
<#  
.SYNOPSIS  
    Removes client from WSUS.
.DESCRIPTION
    Removes client from WSUS.
.PARAMETER Computer
    Name of the client to remove from WSUS.
.PARAMETER InputObject
    Computer object that is being removed.     
.NOTES  
    Name: Remove-WSUSClient
    Author: Boe Prox
    DateCreated: 12NOV2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Remove-WSUSClient -computer "server1"

Description
-----------      
This command will remove 'server1' from WSUS. 
.EXAMPLE  
Get-WSUSClient -computer "server1" | Remove-WSUSClient

Description
-----------      
This command will remove 'server1' from WSUS. 
.EXAMPLE  
Get-WSUSClient -computer "servers" | % {Remove-WSUSClient -inputobject $_}

Description
-----------      
This command will remove multiple servers from WSUS. 
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'collection',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True    
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'string',
            ValueFromPipeline = $True)]
            [string]$Computer,
        [Parameter(            
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'collection',
            ValueFromPipeline = $True)]
            [system.object]
            [ValidateNotNullOrEmpty()]
            $InputObject                                              
            )                
$ErrorActionPreference = 'continue' 
Switch ($pscmdlet.ParameterSetName) {        
   "string" {
        Write-Verbose "String parameter"
        #Retrieve computer in WSUS
        Try { 
            Write-Verbose "Searching for computer"     
            $client = $wsus.SearchComputerTargets($computer)
            }
        Catch {
            Write-Error "Unable to retrieve $($computer) from database."
            } 
        }
    "Collection" {
        Write-Verbose "Collection parameter"
        $client = $inputobject
        }
    }                               
#Remove client from WSUS
If ($pscmdlet.ShouldProcess($computer)) {
    Try {
        Write-Verbose "Removing computer from WSUS"
        $client | % -process {$_.Delete()}
        Write-Host -fore Green "$($Client.FullDomainName) removed from WSUS"
        }
    Catch {
        Write-Error "Unable to remove $($client.fulldomainname) from WSUS."
        }  
    }                 
}

function Get-WSUSClientGroupMembership {
<#  
.SYNOPSIS  
    Lists all Target Groups that a client is a member of in WSUS.
.DESCRIPTION
    Lists all Target Groups that a client is a member of in WSUS.
.PARAMETER Computer
    Name of the client to check group membership.
.PARAMETER InputObject
    Computer object being used to check group membership.     
.NOTES  
    Name: Get-WSUSClientGroupMembership
    Author: Boe Prox
    DateCreated: 12NOV2010 
           
.LINK  
    https://boeprox.wordpress.org
.EXAMPLE  
Get-WSUSClientGroupMembership -computer "server1"

Description
-----------      
This command will retrieve the group membership/s of 'server1'. 
.EXAMPLE  
Get-WSUSClient -computer "server1" | Get-WSUSClientGroupMembership

Description
-----------      
This command will retrieve the group membership/s of 'server1'. 
.EXAMPLE  
Get-WSUSClient -computer "servers" | % {Get-WSUSClientGroupMembership -inputobject $_}

Description
-----------      
This command will retrieve the group membership/s of each server. 
       
#> 
[cmdletbinding(
	DefaultParameterSetName = 'collection',
	ConfirmImpact = 'low',
    SupportsShouldProcess = $True    
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'string',
            ValueFromPipeline = $True)]
            [string]$Computer,
        [Parameter(            
            Mandatory = $True,
            Position = 0,
            ParameterSetName = 'collection',
            ValueFromPipeline = $True)]
            [system.object]
            [ValidateNotNullOrEmpty()]
            $InputObject                                              
            )                
$ErrorActionPreference = 'continue' 
Switch ($pscmdlet.ParameterSetName) {        
   "string" {
        Write-Verbose "String parameter"
        #Retrieve computer in WSUS
        Try { 
            Write-Verbose "Searching for computer"     
            $client = $wsus.SearchComputerTargets($computer)
            }
        Catch {
            Write-Error "Unable to retrieve $($computer) from database."
            } 
        }
    "Collection" {
        Write-Verbose "Collection parameter"
        $client = $inputobject
        }
    } 
#List group membership of client
$client | % -process {$_.GetComputerTargetGroups()}
}
Posted in Modules, powershell, WSUS | Tagged , , | 25 Comments

Quick-Hits: Find currently logged on users

I was recently tasked with locating all servers on our network and query for users that were currently logged onto each server, either through a terminal session or logged on via console session.  This got me thinking of what ways are available to make this happen.  When it was all said and done, I came up with 4 ways to do this.

The first method is to use the Win32_ComputerSystem and grab the UserName property. The thing that you keep in mind with is that this will only return the user that is logged on using a console session, meaning that they are locally logged onto the machine, not logged on via remote desktop.

Untitled

The second method involves another WMI query that will work for both console sessions and remote sessions.  This query looks at the Win32_Process class and then performs a query to look for all of the explore.exe process, which is the user shell for each user that is logged into the server.  Using this query, you can perform a wmi search and then create a custom object to hold the data. I used an advanced function I wrote to perform the query. This gets the job done and shows you who is logged into the machine, but it doesn’t really give you a lot of information to work with.

Untitled

Function Get-WMIComputerSessions {
<#
.SYNOPSIS
    Retrieves tall user sessions from local or remote server/s
.DESCRIPTION
    Retrieves tall user sessions from local or remote server/s
.PARAMETER computer
    Name of computer/s to run session query against.
.NOTES
    Name: Get-WmiComputerSessions
    Author: Boe Prox
    DateCreated: 01Nov2010

.LINK
    https://boeprox.wordpress.org
.EXAMPLE
Get-WmiComputerSessions -computer "server1"

Description
-----------
This command will query all current user sessions on 'server1'.

#>
[cmdletbinding(
	DefaultParameterSetName = 'session',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ValueFromPipeline = $True)]
            [string[]]$computer
    )
Begin {
    #Create empty report
    $report = @()
    }
Process {
    #Iterate through collection of computers
    ForEach ($c in $computer) {
        #Get explorer.exe processes
        $proc = gwmi win32_process -computer $c -Filter "Name = 'explorer.exe'"
        #Go through collection of processes
        ForEach ($p in $proc) {
            $temp = "" | Select Computer, Domain, User
            $temp.computer = $c
            $temp.user = ($p.GetOwner()).User
            $temp.domain = ($p.GetOwner()).Domain
            $report += $temp
          }
        }
    }
End {
    $report
    }
}

The third method is made using the query sessions command line, which is available in Vista and above OS’s and on systems running Terminal Servers. Just using this command line will return a string value which does list out a nice amount of information sessiontype, username, active state of the session, etc…:

query session /server:"boe-laptop"

Untitled

This is nice and all, but I would rather return an object that I can sort or export into a csv or something else. So with that I went and created this advanced function to parse the data and make into a more usable object:

Function Get-ComputerSessions {
<#
.SYNOPSIS
    Retrieves tall user sessions from local or remote server/s
.DESCRIPTION
    Retrieves tall user sessions from local or remote server/s
.PARAMETER computer
    Name of computer/s to run session query against.
.NOTES
    Name: Get-ComputerSessions
    Author: Boe Prox
    DateCreated: 01Nov2010

.LINK
    https://boeprox.wordpress.org
.EXAMPLE
Get-ComputerSessions -computer "server1"

Description
-----------
This command will query all current user sessions on 'server1'.

#>
[cmdletbinding(
	DefaultParameterSetName = 'session',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ValueFromPipeline = $True)]
            [string[]]$computer
            )
Begin {
    $report = @()
    }
Process {
    ForEach($c in $computer) {
        # Parse 'query session' and store in $sessions:
        $sessions = query session /server:$c
            1..($sessions.count -1) | % {
                $temp = "" | Select Computer,SessionName, Username, Id, State, Type, Device
                $temp.Computer = $c
                $temp.SessionName = $sessions[$_].Substring(1,18).Trim()
                $temp.Username = $sessions[$_].Substring(19,20).Trim()
                $temp.Id = $sessions[$_].Substring(39,9).Trim()
                $temp.State = $sessions[$_].Substring(48,8).Trim()
                $temp.Type = $sessions[$_].Substring(56,12).Trim()
                $temp.Device = $sessions[$_].Substring(68).Trim()
                $report += $temp
            }
        }
    }
End {
    $report
    }
}

So now when I run this, I have my custom object that can be ran on multiple machines and list out much more information than my previous function.

Untitled

The fourth and final way that I found to do this was using a freely available Terminal Services module, built by Shay Levi to query for user sessions. As you can see from the output, it works rather nicely.

Import-Module PSTerminalServices
Get-TSSession -ComputerName "dc1"

Untitled

As you can see, there are a variety of ways to gather information on user sessions on local and remote machines.  Some are very basic, but will work from any workstation/server, while others contain more information, but may only be available on certain systems.

Posted in powershell, scripts | Tagged , , , | 28 Comments

WSUS: Managing Groups with PowerShell

In this post, I will show how to use PowerShell to manage the Target Groups in WSUS.  Using PowerShell, you can Create and Delete groups and Add/Remove clients from groups.

First, we need to setup our connection to our WSUS server:

$wsusserver = 'dc1'
#Load required assemblies            
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$False)

Create Target Group

We will be using the CreateComputerTargetGroup() method to create our group. There are two different ways to use this method, one just requires a name for the Group and the other allows you to supple a parent group name to create a child group underneath it.

If you want to validate the group name to make sure it will be ok to use in WSUS, you can use the IsValidComputerTargetGroupName() method to see if WSUS will allow the group name.

$wsus.IsValidComputerTargetGroupName("Te$t")
$wsus.IsValidComputerTargetGroupName("Test@")

Untitled

Let’s create a group now:

$wsus.CreateComputerTargetGroup("TestGroup")

Simple enough. After you create the group, you will receive confirmation of the creation that lists the group name and the ID GUID of the group.

Untitled

Now that we have created this test group, lets create a second group underneath that group.

We need to first get the group that we created.

$group = $wsus.GetComputerTargetGroups() | ? {$_.Name -eq "TestGroup"}

Untitled

Now with that,we can create the child group underneath our newly created parent group:

$wsus.CreateComputerTargetGroup("ChildGroup",$group)

As usual, you can immediately see the new group along with its GUID.

Untitled 

It isn’t that apparent that this is a child group until you see the console:

Untitled

You can also use the GetChildTargetGroups() method that is available to find out if a group has any children. It is important to note that you must call this on an individual group, not the entire collection unless you loop through each group in the collection.

$group.GetChildTargetGroups()

Untitled

Delete Target Group

Ok, so we have figured out how to create groups within WSUS, but how do you delete the groups?  Well, the answer is pretty easy. We will make use of the Delete() method that is available for each group in the collection. Lets get the child group we created and then delete it using the Delete method:

$group = $wsus.GetComputerTargetGroups() | ? {$_.Name -eq "ChildGroup"}
$group.Delete()

 

Now, the child group is gone.

Untitled

Creating a target is fairly simple with an little bit of complexity when your adding a child group, but deleting a group is pretty painless. Now what happens if you delete the parent group and not the child? Easy, both are deleted.

Add Computer to Target Group

Lets now take a look at adding a client to a target group.  For this, we will be using the AddComputerTarget() method that is available for each group in the collection. Looking at the requirements for this method, we can see that the value that it is expecting is a computer target object. So just typing in the name of the computer will not work and will only throw an error.

Untitled

The quickest way to get a client is by using the GetComputerTargetByName() method. This only works as long as you know the client name. This method has by far the best performance of locating a client in WSUS using PowerShell instead of using the GetComputerTargets() method and throwing in a Where-Object to locate the name. I am going to add “boe-laptop” to the “Domain Servers” group in my example.

$client = $wsus.GetComputerTargetByName("boe-laptop")
$group.AddComputerTarget($client)

Untitled

If you wanted to add more than one computer to a group, you will have to create the collection of clients and then iterate through the collection and add each on into the group.

$clients = $wsus.GetComputerTargets()
ForEach ($client in $clients) {
Group.AddComputerTarget($client)
}

Remove Computer from Target Group

Last in this post I will show you how to remove a computer from a group using the RemoveComputerTarget() which is available for each group in the collection. Just like when we added a client to a group, we will once again need to first get the computer object to meet the required value of the method. For this example, I will remove “boe-latop” from the “Domain Servers” group.

$client = $wsus.GetComputerTargetByName("boe-laptop")
$group.RemoveComputerTarget($client)

Untitled

And, just like with adding multiple clients to a group, you will need to iterate through the collection of clients to remove each one from a group.

$clients = $wsus.GetComputerTargets()
ForEach ($client in $clients) {
Group.RemoveComputerTarget($client)
}

Once you start to climb into managing WSUS groups with PowerShell, it really is just a matter of a few lines to start to make things happen.

Posted in powershell, WSUS | Tagged , , | 7 Comments

WSUS: Approving and Declining Updates with PowerShell

Now that we have looked at viewing all of the updates on WSUS, we will now look at approving and declining those updates.  This can be a little bit tricky as you need to also know what Computer Target Group you want to approve each update for.  With that, lets take a quick look at how to locate the computer groups in WSUS.

Lets first make the connection to the WSUS server and then we can look at finding those groups:

$wsusserver = 'dc1'
#Load required assemblies
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$False)

Untitled

Now to get the groups.  Using the $wsus variable, we see that there is a method called GetComputerTargetGroups() which we will use to list out all of the groups with their associated ID’s.

$wsus.GetComputerTargetGroups()

Untitled

As you can see, I have 3 groups in WSUS. The 2 default groups: Unassigned Computers and All Computers along with a group I added called Domain Servers. Now that we know how to get the groups, we can take a look at approving updates for a specific group.

First I am going to select just 1 update to approve.

$update = $wsus.GetUpdates() | Select -last 1

Untitled

Looking at the IsApproved property, it is currently set to False, meaning it has yet to be approved for installation.

So we know that this update has not been approved yet and we need to approve it for the Domain Servers target group. So…how do we do this?  First we need to look at the Approve() method under the $update variable to find out what type of data it accepts.

Untitled

We can see there are 2 ways of approving this patch:

  1. Supply the Action and Group
  2. Supply the Action, Group and a deadline

Let’s focus on the first way of approving the update.

Looks easy enough, just have to do $update.approve(“Install”,”Domain Server”) and we should be set…

Untitled

Hmm… now that is something we don’t need to see.  Looking back at the required values for the method, you will see the type of value that is expected for the method.

I can use the Action required type to find out what the accepted data is:

[Microsoft.UpdateServices.Administration.UpdateApprovalAction] | gm -static

Untitled

We see that there are 4 different values to choose from: All, Install, NotApproved, Uninstall

But wait, didn’t we try Install already and it failed? Yes, we did because we presented it as a string type, not as the type that was required.  so in order to accomplish this we do the following:

#Define the actions available for approving a patch
$all = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::All
$install = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Install
$NotApproved = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::NotApproved
$Uninstall = [Microsoft.UpdateServices.Administration.UpdateApprovalAction]::Uninstall

Excellent, now looking back at what is required for the groups, we can tell that a simple string type will not suffice and that we need the actual object for groups.  Since we only want the one group, Domain Servers, we will need to only grab that group.

#List all available groups and their respective IDs
$wsus.GetComputerTargetGroups()
#Pick a group to approve an update for
$group = $wsus.GetComputerTargetGroups() | ? {$_.Name -eq "Domain Servers"}

Untitled

Ok, now we only have the Domain Servers group to approve our update for. By the way, the “?” being used is short-hand for Where-Object.

Lets give this another shot now:

#Approve the update
$update.Approve($install,$group)

Untitled

Success this time around.  As you can see, once you approve the update, you get some additional output to verify that your update was approved.  From here, you can tell when the update will be made available to clients for download by looking at the GoLiveTime property. In this case, the update will be made available on 10/16/2010 9:12:33 PM. Another interesting thing to note is the AdministratorName property, which tells you who approved the update. Also note that the Deadline property shows a timestamp of 12/31/9999 11:59:59 PM, which is because we did not specify a deadline.

To specify a deadline, you need to configure the deadline by using the Get-Date cmdlet or the [datetime] .NET accelerator. For this, I will set a deadline for Oct. 20 at 5pm.

#Configure Deadline
$deadline = [datetime]"10/20/2010 5:00PM"
#Approve the update with deadling
$update.Approve($install,$group,$deadline)

Untitled

Now the Deadline property shows 10/20/2010 5:00:00 PM.

If you want to see the Approval for an update, you can use the GetUpdateApprovals() method.

$update.GetUpdateApprovals()

Untitled

The one thing that I see here that might be confusing is the ComputerTargetGroupId which is the group that is update has been approved for. There are a couple of ways you can find out which group this translates to. Either using the code near the beginning of this post to list out all of the groups and locate the correct one:

$wsus.GetComputerTargetGroups() | ? {$_.ID -eq "76a6de42-3d93-4bbc-a8a4-76de3cc0c58f"}

Untitled

Or I have a function that is included in my WSUS module that I am still working on that will also perform the task.

function Convert-WSUSTargetGroup {
<#
.SYNOPSIS
    Converts the ID to a friendl name or friendly name to an ID
.DESCRIPTION
    Converts the ID to a friendl name or friendly name to an ID
.PARAMETER wsusserver
    Name of WSUS server to connect to.
.PARAMETER id
    Determines the name of the group using the supplied ID.
.PARAMETER name
    Determines the ID of the group using the supplied name.
.NOTES
    Name: Convert-WSUSTargetGroup
    Author: Boe Prox
    DateCreated: 24SEPT2010
.LINK
    https://boeprox.wordpress.org
.EXAMPLE
 Convert-WSUSTargetGroup -wsusserver 'server1' -name 'All Computers'
#>
[cmdletbinding(
	DefaultParameterSetName = 'name',
	ConfirmImpact = 'low'
)]
    Param(
        [Parameter(
            Mandatory = $True,
            Position = 0,
            ValueFromPipeline = $True)]
            [string]$wsusserver,
        [Parameter(
            Mandatory = $False,
            Position = 1,
            ParameterSetName = 'id',
            ValueFromPipeline = $False)]
            [string]$id,
        [Parameter(
            Mandatory = $False,
            Position = 2,
            ParameterSetName = 'name',
            ValueFromPipeline = $False)]
            [string]$name
            )
#Load required assemblies
[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")
#Connect to WSUS server
$wsus = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($wsusserver,$False)
If ($name) {
    Try {
        Write-Verbose "Searching for ID via name"
        $group = $wsus.GetComputerTargetGroups() | ? {$_.Name -eq $name}
        $group | Select -ExpandProperty ID
        Continue
        }
    Catch {
        Write-Error "Unable to locate $($name)."
        }
    }
If ($id) {
    Try {
        Write-Verbose "Searching for name via ID"
        $group = $wsus.GetComputerTargetGroups() | ? {$_.ID -eq $id}
        $group | Select -ExpandProperty Name
        Continue
        }
    Catch {
        Write-Error "Unable to locate $($id)."
        }
    }
}

Function in action:

Untitled

This code snippet will report every update that has been approved and will list out the name of the update, deadline, Admin that approved it and the group that it was approved for:

$updates = $wsus.GetUpdates() | ? {$_.IsApproved -eq "True"}
$report = @()
ForEach ($update in $updates) {
    $approval = $update.GetUpdateApprovals() | % {
            $id = $_.ComputerTargetGroupId
            $temp = "" | Select Title, Group, Deadline, ApprovedBy, AvailableForDownload
            $temp.Title = $update.Title
            $temp.Deadline = $_.Deadline
            $temp.ApprovedBy = $_.AdministratorName
            $temp.AvailableForDownload = $_.GoLiveTime
            $temp.Group = $wsus.GetcomputerTargetGroups() | ? {$_.ID -eq $id} | Select -expand Name
            $report += $temp
        }
    }
$report

Untitled

Declining an update is much easier and requires nothing more than a boolean operation.

$update.Decline($True)

Untitled

Now we have declined this update. Pretty simple, eh?

As you can see, you can approve and decline updates using PowerShell and view all of the groups that have been created in WSUS with just a small amount of code.

Posted in powershell, WSUS | Tagged , , , , | 3 Comments