Multithreading help

Oleg Aronov 131 Reputation points
2021-10-28T15:12:49.707+00:00

Hello All,

I need to collect login info from bunch of servers and have script to do it.
However, since I have a big list of servers it takes a lot of time to finish.

I wonder how can I adjust it to use Multithreading.
Ideally I'd like to run against maybe 5 servers at once.

Can someone please help?

Here is my script:

Function Get-LastLoginInfo {

[CmdletBinding(DefaultParameterSetName="Default")]
param(
    [Parameter(
        Mandatory = $false,
        ValueFromPipeline = $true,
        ValueFromPipelineByPropertyName = $true,
        Position = 0
    )]
    [string[]]  $ComputerName = $env:COMPUTERNAME,


    [Parameter(
        Position = 1,
        Mandatory = $false,
        ParameterSetName = "Include"
    )]
    [string]    $SamAccountName,


    [Parameter(
        Position = 1,
        Mandatory = $false,
        ParameterSetName = "Exclude"
    )]
    [string]    $ExcludeSamAccountName,


    [Parameter(
        Mandatory = $false
    )]
    [ValidateSet("SuccessfulLogin", "FailedLogin", "Logoff", "DisconnectFromRDP")]
    [string]    $LoginEvent = "SuccessfulLogin",


    [Parameter(
        Mandatory = $false
    )]
    [int]       $DaysFromToday = 3,


    [Parameter(
        Mandatory = $false
    )]
    [int]       $MaxEvents = 1024,


    [System.Management.Automation.PSCredential]
    $Credential
)


BEGIN {
    $StartDate = (Get-Date).AddDays(-$DaysFromToday)
    Switch ($LoginEvent) {
        SuccessfulLogin   {$EventID = 4624}
        FailedLogin       {$EventID = 4625}
        Logoff            {$EventID = 4647}
        DisconnectFromRDP {$EventID = 4779}
    }
}

PROCESS {
    foreach ($Computer in $ComputerName) {
    Write-Host "working on" $Computer

        try {
            $Computer = $Computer.ToUpper()
            $Time = "{0:F0}" -f (New-TimeSpan -Start $StartDate -End (Get-Date) | Select -ExpandProperty TotalMilliseconds) -as [int64]

            if ($PSBoundParameters.ContainsKey("SamAccountName")) {
                $EventData = "
                    *[EventData[
                            Data[@Name='TargetUserName'] != 'SYSTEM' and
                            Data[@Name='TargetUserName'] != '$($Computer)$' and
                            Data[@Name='TargetUserName'] = '$($SamAccountName)'
                        ]
                    ]
                "
            }

            if ($PSBoundParameters.ContainsKey("ExcludeSamAccountName")) {
                $EventData = "
                    *[EventData[
                            Data[@Name='TargetUserName'] != 'SYSTEM' and
                            Data[@Name='TargetUserName'] != '$($Computer)$' and
                            Data[@Name='TargetUserName'] != '$($ExcludeSamAccountName)'
                        ]
                    ]
                "
            }

            if ((-not $PSBoundParameters.ContainsKey("SamAccountName")) -and (-not $PSBoundParameters.ContainsKey("ExcludeSamAccountName"))) {
                $EventData = "
                    *[EventData[
                            Data[@Name='TargetUserName'] != 'SYSTEM' and
                            Data[@Name='TargetUserName'] != '$($Computer)$'
                        ]
                    ]
                "
            }

            $Filter = @"
                <QueryList>
                    <Query Id="0">
                        <Select Path="Security">
                        *[System[
                                Provider[@Name='Microsoft-Windows-Security-Auditing'] and
                                EventID=$EventID and
                                TimeCreated[timediff(@SystemTime) &lt;= $($Time)]
                            ]
                        ]
                        and
                            $EventData
                        </Select>
                    </Query>
                </QueryList>

"@

            if ($PSBoundParameters.ContainsKey("Credential")) {
                $EventLogList = Get-WinEvent -ComputerName $Computer -FilterXml $Filter -Credential $Credential -ErrorAction Stop
              } else {
                $EventLogList = Get-WinEvent -ComputerName $Computer -FilterXml $Filter -ErrorAction Stop
            }


            $Output = foreach ($Log in $EventLogList) {
                #Removing seconds and milliseconds from timestamp as this is allow duplicate entries to be displayed
                $TimeStamp = $Log.timeCReated.ToString('MM/dd/yyyy hh:mm tt') -as [DateTime]

                switch ($Log.Properties[8].Value) {
                    2  {$LoginType = 'Interactive'}
                    3  {$LoginType = 'Network'}
                    4  {$LoginType = 'Batch'}
                    5  {$LoginType = 'Service'}
                    7  {$LoginType = 'Unlock'}
                    8  {$LoginType = 'NetworkCleartext'}
                    9  {$LoginType = 'NewCredentials'}
                    10 {$LoginType = 'RemoteInteractive'}
                    11 {$LoginType = 'CachedInteractive'}
                }

                if ($LoginEvent -eq 'FailedLogin') {
                    $LoginType = 'FailedLogin'
                }

                if ($LoginEvent -eq 'DisconnectFromRDP') {
                    $LoginType = 'DisconnectFromRDP'
                }

                if ($LoginEvent -eq 'Logoff') {
                    $LoginType = 'Logoff'
                    $UserName = $Log.Properties[1].Value.toLower()
                } else {
                    $UserName = $Log.Properties[5].Value.toLower()
                }


                [PSCustomObject]@{
                    ComputerName = $Computer
                    TimeStamp    = $TimeStamp
                    UserName     = $UserName
                    LoginType    = $LoginType
                }
            }

            #Because of duplicate items, we'll append another select object to grab only unique objects
            $Output | select ComputerName, TimeStamp, UserName, LoginType -Unique | select -First $MaxEvents

        } catch {
            Write-Error $_.Exception.Message

        }
    }
}

END {}

}

$sourceCsvFilePath = 'C:***\Hosts.csv'

$destinationDirectory = 'C:\'

$CM = import-csv -Path $sourceCsvFilePath | Select-Object -ExpandProperty hostname

$reportName = "Logins.csv"
$FullPathToReport = "$destinationDirectory\$reportName"

$Result = Get-LastLoginInfo -ComputerName $CM -DaysFromToday 5

$Result | Export-Csv $destinationDirectory\$reportName

Thank you!

Windows Server
Windows Server
A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.
13,675 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Limitless Technology 39,796 Reputation points
    2021-10-29T16:35:25.273+00:00

    Hello @Oleg Aronov ,

    Thank youbfir reaching out.

    In Powershell 7 you can use ForEach-Object -Parallel with ThrottleLimit
    Parameter.

    Sample script


    $Message = "Output:" Get-ChildItem $dir | ForEach-Object -Parallel { "$using:Message $_" } -ThrottleLimit 4


    Below is Microsoft reference article explaining howvto use threads with ForEach-Object.

    https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object?view=powershell-7

    ------

    --If the reply was helpful, please don’t forget to upvote or accept as answer.--

    1 person found this answer helpful.

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.