Get Function to process a list

This topic contains 1 reply, has 2 voices, and was last updated by  Dave Wyatt 3 years, 1 month ago.

  • Author
  • #22599

    Leonard Hopkins

    I found this script to produce uptime on a computer. The original script starts at the "Function Get-Uptime {" line and the original $ComputerName variable was set to $ENV:ComputerName.

    The script works but I want to process a list of computers. That's why I changed the $ComputerName variable to $Server and added the first three lines (including the "{" and a final "}".

    The script processes but only displays the last name in my list. It is not processing all the names in my list. How can I get it to process a list instead of just one machine?

    $Servers=Get-Content C:\temp\ServersTest.txt
    ForEach ($Server in $Servers)
    Function Get-Uptime {

    [Parameter(ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
    [String]$ComputerName = $Server,

    [Int32]$Days = 41,


    Begin {
    $StartUpID = 6005
    $ShutDownID = 6006
    $StartingDate = (Get-Date).Date.AddDays(-$Days)
    Process {

    If ($Ping) {
    $PingResult = Test-Connection $ComputerName -Quiet -Count 1
    } Else {
    # Fabricate success, we're not running this test
    $PingResult = $True

    Try {
    $EventLogData = Get-EventLog -LogName System -After $startingDate -Source EventLog -EntryType Information -ComputerName $ComputerName |
    Where-Object { $_.EventID -eq $StartUpID -Or $_.EventID -eq $ShutDownID }
    } Catch { }

    # If the previous command was successful (we didn't catch an exception) and $PingResult is true (see the note above about use)
    If ($? -And $PingResult) {

    # Initialise the counters
    $Uptime = [TimeSpan]0; $DownTime = [TimeSpan]0

    # To speed up execution or it'll enumerate this collection every time it loops
    $Count = $EventLogData.Count
    # The data should be sorted, but we should not assume that all values are correctly paired
    For ($i = 0; $i -lt $Count; $i++) {

    If ($EventLogData[$i].EventID -eq $StartUpID -And $EventLogData[$i + 1].EventID -eq $ShutDownID) {

    # This is the critical one, sum up all downtime intervals
    $Downtime += New-TimeSpan $EventLogData[$i + 1].TimeGenerated $EventLogData[$i].TimeGenerated
    } ElseIf ($EventLogData[$i].EventID -eq $ShutDownID -And $EventLogData[$i + 1].EventID -eq $StartUpID) {

    # Record this, even if it's not used for calculations.
    $Uptime += New-TimeSpan $EventLogData[$i + 1].TimeGenerated $EventLogData[$i].TimeGenerated
    } Else {
    Write-Debug "Could not correlate index $i"

    # Notes on properties:
    # Uptime – An assumed value. Uptime is assumed to be $Days minus any explicit downtime intervals
    # RecordedUptime – Here for information only, not used in calculations as a server that is up all the
    # time will have no RecodedUptime in the given period.
    # Downtime – From above
    # Percentage – A calculation based on 100% uptime minus each downtime interval
    "" | Select-Object `
    @{n='ComputerName';e={ $ComputerName }},
    @{n='Status';e={ "OK" }},
    @{n='Uptime';e={ (New-TimeSpan -Days $Days) – $DownTime }},
    @{n='RecordedUptime';e={ $Uptime }},
    @{n='Downtime';e={ $Downtime }},
    @{n='Percentage';e={ '{0:P2}' -f (1 – $Downtime.Ticks / (New-TimeSpan -Days $Days).Ticks) }}
    } Else {

    # Create a simple return object
    "" | Select-Object `
    @{n='ComputerName';e={ $ComputerName }},
    @{n='Status';e={ If (!$PingResult) { "Ping failed" } Else { $Error[0].Exception.Message.Trim() } }},
    Uptime, RecordedUptime, Downtime, Percentage

  • #22601

    Dave Wyatt

    You don't need to put your function definition inside a loop like that; you only need calls to the function there. In the function's param block, the "= $env:COMPUTERNAME" part was just setting a default value that the function will use if the caller doesn't specify a computer name, but you can override that by passing specific computer names to the function. Here are a few ways that might work:

    $Servers=Get-Content C:\temp\ServersTest.txt
    # Putting the calls to the function inside a foreach loop
    foreach ($Server in $Servers)
        Get-Uptime -ComputerName $Server
    # piping input into the function (if it supports pipeline input; in this case, it does.)
    $Servers | Get-Uptime
    # Passing the whole array in a single call to the function without pipeline input.
    # This won't work with Get-Uptime the way it's currently written, because $ComputerName is
    # defined as a [string] instead of a [string[]] array.
    Get-Uptime -ComputerName $Servers

    Here's the standard way of writing a function so it can support all three of these calling styles:

    function Get-Uptime
        # Note that the parameter type is an array
        param (
            [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
            [string[]] $ComputerName
        # Functions that accept pipeline input must have a process block to work properly
            # For functions that you want to have support PowerShell 2.0, IF the parameter is allowed
            # to be $null, you need to put a check for $null outside the foreach loop.  Otherwise the
            # loop will execute once with the loop variable set to $null, which is almost certainly
            # not what you want.  This bug is fixed in PowerShell 3.0 and later.
            if ($ComputerName)
                # Because $ComputerName is an array, we need to have a loop inside the process block
                # to handle each value individually.
                foreach ($computer in $ComputerName)
                    # Do something with $computer

You must be logged in to reply to this topic.