Share via

Multithreading help

olegarr 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 for business | Windows Server | User experience | Other
0 comments No comments

1 answer

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

    Hello @olegarr ,

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

    Was this answer helpful?

    1 person found this answer helpful.

Your answer

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