Author Posts

February 13, 2015 at 9:07 am

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 {

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

[Int32]$Days = 41,

[Switch]$Ping
)

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
}
}
}
}

February 13, 2015 at 9:40 am

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
    process
    {
        # 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
            }
        }
    }
}