5
\$\begingroup\$

I work in a Windows environment where Scheduled Tasks have never been documented or kept up with, leaving us with about 150 servers that have a random smattering of tasks. I am attempting to construct something that will query specific information about these tasks and output them in a pretty format that I can sort through.

Here is what I have currently cobbled together:

Import-Module ActiveDirectory
$VerbosePreference = "continue"
$list = (Get-ADComputer -LDAPFilter "(&(objectcategory=computer)(OperatingSystem=*server*))").Name
$logfilepath = "$home\Desktop\TasksLog.txt"
$ErrorActionPreference = "SilentlyContinue"

invoke-command -ComputerName $list -ScriptBlock { $sched = New-Object -Com "Schedule.Service"
$sched.Connect()
$out = @()
$sched.GetFolder("\").GetTasks(0) | % {
    $xml = [xml]$_.xml
    $out += New-Object psobject -Property @{
        "Name" = $_.Name
        "Status" = switch($_.State) {0 {"Unknown"} 1 {"Disabled"} 2 {"Queued"} 3 {"Ready"} 4 {"Running"}}
        "NextRunTime" = $_.NextRunTime
        "LastRunTime" = $_.LastRunTime
        "Author" = $xml.Task.Principals.Principal.UserId
    }
}

$out | fl Name,Status,NextRunTime,LastRunTime,Author, } | Out-File $logfilepath

The "pretty" part is a work in progress. However, if I am correct, this should query a list of server names from AD and then invoke the command on all of them, then output the results to the designated file. I have tested with one server...

$list = "Server1"

...but am hesitant to attempt to invoke these all at once. I am not even sure if this is the correct/most effective way of passing a list of servers to the invoke-command cmdlet.

\$\endgroup\$
2
\$\begingroup\$

Fan-out remoting

Calling Invoke-Command with an array for -ComputerName is perfectly fine. Note that it's going to call them one by one. If it can't reach some, there will be errors, but it's fine. It's sort of like doing:

$list | ForEach-Object {
    Invoke-Command -ComputerName $_ -ScriptBlock { ... }
}

The above can be useful for helping you with progress, like if you wanted to do something like:

$list | ForEach-Object {
    Write-Verbose $_ -Verbose
    Invoke-Command -ComputerName $_ -ScriptBlock { ... }
}

Or you could turn it into a for loop instead so you know the index, then you can calculate percentage and use Write-Progress to get some really nice progress info.

As for how effective it is, well, very, as long as you don't have a lot of servers and/or don't care how long it takes, since it's not going to run in parallel on all the machines.

Other options for parallel remoting

Workflows

You could use a PowerShell workflow, which has capability to process things remotely and in parallel, but to be honest it's a little weird at first, and probably not necessary.

Jobs

Probably the easiest way to implement parallel jobs is to use PSJobs. You would basically add the -AsJob parameter to Invoke-Command, but then its output is the job object. You have to retool things a bit to then wait on and receive the jobs and the data they return.

Jobs are a bit heavy though; they're technically entirely separate PowerShell processes, so if you have 2,000 servers, it might not be a good idea to suddenly spawn 2,000 powershell processes on your workstation to host the jobs, so be weary of that. Jobs would mostly be useful if you have ~100 or fewer machines, a good workstation, and maybe a lot of the servers timeout (so they take longer than the remote process needs to even run, and choke the whole thing).

Runspaces

PowerShell MVP Boe Prox has a module called PoshRSJob that use runspaces as a more efficient way of multithreading in PowerShell, and it supports throttling too.

Just don't

Since you're just trying to generate a report that you'll look at later, I'd say keep it simple and just don't do it in parallel.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.