How to use Write-Progress in my function

This topic contains 2 replies, has 2 voices, and was last updated by Profile photo of I Am Sir Ask Alot I Am Sir Ask Alot 1 year, 9 months ago.

  • Author
    Posts
  • #26944
    Profile photo of I Am Sir Ask Alot
    I Am Sir Ask Alot
    Participant

    I have never used Write-Progress before and I am having a heck of a time trying to figure out how I can toss it in my existing script below since it takes a while to run on systems that have many updates installed. I would like to throw in a ProgressBar switch parameter so the user can have the option of including it or not.

    Function Get-WindowsUpdates
    		{
    			Param (
    				
    				$ComputerName = $env:COMPUTERNAME
    				
    			)
    			
    			$ComputerName = $ComputerName.ToUpper()
    			
    			Foreach ($Computer in $ComputerName)
    			{
    				$Session = [activator]::CreateInstance([type]::GetTypeFromProgID(“Microsoft.Update.Session”, $Computer))
    				$Searcher = $Session.CreateUpdateSearcher()
    				$History = $Searcher.GetTotalHistoryCount()
    				$Query = $Searcher.QueryHistory(0, $History)
    				
    				ForEach ($Item in $Query)
    				{
    					$Properties = New-Object -Type PSObject -Property @{
    						'ComputerName' = $Computer
    						'UpdateDate' = $Item.Date
    						'KB' = [regex]::match($Item.Title, 'KB(\d+)')
    						'UpdateTitle' = $Item.Title
    						'UpdateDescription' = $Item.Description
    						'SupportUrl' = $Item.SupportUrl
    						'UpdateId' = $Item.UpdateIdentity.UpdateId
    						'RevisionNumber' = $Item.UpdateIdentity.RevisionNumber
    					}
    					
    					Write-Output $Properties
    				}
    			}
    		}

    Thank you

  • #26951
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    Basically, you can't show the progress of a query. Display actual progress requires you to process each item individually. If you look at most Write-Progress examples, you will see they are processing files and you can do the math because you are processing each file in a collection. In your function, you are running a query against the computer and collecting information, but you are NOT getting records one at a time, you are returning a result based on that query. To get the progress you are looking for, you would need to say "Give me the first WIndows Update out of all of the Updates, when I process this record, increment the %". All of the processing time of your function is here: $Query = $Searcher.QueryHistory(0, $History). When the query returns the results, you are simply creating a PSObject, which just takes a couple of seconds.

    Even you wrap the query with a do\while loop and increment it, it's just fake progress because you don't know how long the query is going to take or where it's at in the process. You can put manual progress in steps, but there isn't anyway to provide actual progress on the query itself:

    Function Get-WindowsUpdates	{
    	Param (
    		[string[]]$ComputerName = $env:COMPUTERNAME,
            [switch]$ShowProgress
    	)
    	begin{}		
    	process{
            Foreach ($Computer in $ComputerName) {
                if ($ShowProgress) {write-progress -activity "Connecting to $Computer" -status "25% Complete:" -percentcomplete 25;}		    
                $Session = [activator]::CreateInstance([type]::GetTypeFromProgID(“Microsoft.Update.Session”, $Computer))
                if ($ShowProgress) {write-progress -activity "Connected to $Computer, creating update searcher..." -status "50% Complete:" -percentcomplete 50;}		    
    		    $Searcher = $Session.CreateUpdateSearcher()
    		    $History = $Searcher.GetTotalHistoryCount()
                if ($ShowProgress) {write-progress -activity "Found $History updates on $Computer, beginning update query..." -status "75% Complete:" -percentcomplete 75;}		    
    		    $Query = $Searcher.QueryHistory(0, $History)
    				
    		    $results = ForEach ($Item in $Query) {
    			   $props =  @{
    				    'ComputerName' = $Computer.ToUpper()
    				    'UpdateDate' = $Item.Date
    				    'KB' = [regex]::match($Item.Title, 'KB(\d+)')
    				    'UpdateTitle' = $Item.Title
    				    'UpdateDescription' = $Item.Description
    				    'SupportUrl' = $Item.SupportUrl
    				    'UpdateId' = $Item.UpdateIdentity.UpdateId
    				    'RevisionNumber' = $Item.UpdateIdentity.RevisionNumber
    			    }
    					
    			    New-Object -TypeName PSObject -Property $props
    		    }
    	    }
            if ($ShowProgress) {write-progress -activity "Completed Windows update query on $Computer" -Completed}		    
    
        } #process
        end{$results}
    }
    
    Get-WindowsUpdates -ShowProgress
    

    As a side note, your $ComputerName parameter would be a string array since you are doing a foreach computer. The .ToUpper() is a string method for an individual string, so:

    PS C:\Windows\System32\WindowsPowerShell\v1.0> [string[]]$Computers = "Computer1","Computer2"
    
    PS C:\Windows\System32\WindowsPowerShell\v1.0> $Computers.ToUpper()
    COMPUTER1
    COMPUTER2
    

    The only reason that works is because Powershell V3 introduced implicit foreach, so it assumes you want to run .ToUpper() on each line. If you run that code on a V2 machine it would generate an error. I'd highly recommend doing that inside your foreach loop on individual objects as a best practice.

  • #26959
    Profile photo of I Am Sir Ask Alot
    I Am Sir Ask Alot
    Participant

    That's what I was thinking as well, just wanted confirmation. Yeah, I know about the implicit foreach in PS3 and I am trying to purposely make my scripts not run on PS2 if at all possible. I'm trying to force those I work with to upgrade to at least PS3 right now. I usually throw in a #Requires -Version 3.0. I didn't know it was a best practice, but I guess that makes sense. I also changed the "$Properties = New-Object -Type PSObject -Property" portion to [pscustomobject] since I am trying to force the use of PowerShell 3 on everyone.

    Thanks for your help.

You must be logged in to reply to this topic.