Search code examples
powershellforeachpowershell-5.0

Powershell foreach regarding multiple collections


So i have a powershell script im toying with that i'd like to ask the community's help. I have to preface that I am not always the best in communicating what I am attempting to do, partly because i dont have programming experience so please bear with me, and ask questions/correct me if i use incorrect words to explain what i mean.

With that said, Here's what I am trying to do:

while incrementing both services and startuptypes:

  • stop service A ($services) on server X ($rebootingServer)

  • Disable service A ($services) on server X ($rebootingServer)

    Given: we know service A is disabled on server Y prior to script running

  • Enable service A on server Y based on text file list $startuptypes

  • Start service A on server Y
  • Rinse and repeat until $services and $startuptypes are at the end of each list

So assume $services has:
bits appmgmt

and $startuptypes has:

Automatic Manual

i want them to be applied respectively (bits > automatic appmgmt > manual)

Heres what i have thus far:

$services = Get-Content "C:\TEMP\services.txt"
$Startuptypes = Get-Content "C:\TEMP\StartupTypes.txt"
$RebootingServer = Read-Host 'Name of the server that you are bringing down'
$FailoverServer = Read-Host 'Name of the server it is failing over to'


#foreach ($service in $services && $Startuptype in $Startuptypes) {

Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) Stop-Service $service}
Start-Sleep -s 3
Invoke-Command -ComputerName $RebootingServer -ArgumentList $service - ScriptBlock {param($service) set-service $service -StartupType Disabled}
Start-Sleep -s 10
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service $StartupType -ScriptBlock {param($service,$startuptype) Set-Service $service -StartupType $startuptype}
Start-Sleep -s 3
Invoke-Command -ComputerName $FailoverServer -ArgumentList $service - ScriptBlock {param($service) Start-Service $service}
Start-sleep -s 10
}

The 'for each' statement is pseudo-code of what i want it to do, yet unsure if that exists or how to write it accordingly. I dont even know what that would be appropriately called. Multiple conditionals? That aside, how would i properly write what I am attempting to accomplish? Thank you for any help in advanced.


Solution

  • It sounds like you want to enumerate the elements of 2 collections in corresponding pairs: in iteration 1, process element 1 from collection A along with element 1 from collection B, ...

    Note:

    • This answer assumes that the input collections have the same length. See this answer for a solution if they do not.

    PowerShell 6- solution:

    # Sample collections.
    # Note that their counts must match.
    $services     = 'serviceA', 'serviceB', 'serviceC'
    $startupTypes = 'automatic', 'manual', 'disabled '
    
    
    $i = 0 # helper index var.
    foreach ($service in $services) { # enumerate $services directly
    
       # Using the index variable, find the corresponding element from 
       # the 2nd collection, $startupTypes, then increment the index.
       $startupType = $startupTypes[$i++]
    
       # Now process $service and $startupType as needed.
    }
    

    PowerShell 7+ solution:

    PowerShell 7 is built on .NET Core 3.1, where the System.Linq.Enumerable.Zip has an overload that returns (2-element) value tuples (System.ValueTuple<T1, T2>), which simplifies the solution:

    • Caveat: With input collections of unequal length, enumeration stops once the smaller collection has run out of items.
    # Sample collections.
    # Note: Due to use of [Linq.Enumerable]::Zip() below:
    #  * The element counts need *not* match, because pairing stops 
    #    once the shorter collection has run out of elements.
    #  * However, note that strongly typed type constraint ([string[]]),
    #    which is required for PowerShell to find the right [Linq.Enumerable]::Zip()
    #    method overload. You can also *cast* on demand. 
    [string[]] $services     = 'serviceA', 'serviceB', 'serviceC'
    [string[]] $startupTypes = 'automatic', 'manual', 'disabled '
    
    # Enumerate the collection in pairs (2-element value tuples).
    # Note that the properties containing the tuple elements are
    # named .Item1 and .Item2, but PowerShell allows you to access them 
    # by positional index too.
    [Linq.Enumerable]::Zip($services, $startupTypes) | ForEach-Object {
      "service: {0}; startup type: {1}" -f $_[0], $_[1]
    }
    

    Generally, note that PowerShell, as of 7.3, lacks support for .NET extension methods (which is why explicit use of [System.Linq.Enumerable] is required here) and also lacks comprehensive support for calling generic methods, though there are feature requests on GitHub - see GitHub issue #2226 and GitHub issue #5146.

    The above yields:

    service: serviceA; startup type: automatic
    service: serviceB; startup type: manual
    service: serviceC; startup type: disabled