Construir pipelins amb PowerShell 7 i ForEach-Object -Parallel

Darrera actualització: 17/12/2025
Autor: Isaac
  • PowerShell ofereix múltiples formes d'iterar (foreach, ForEach-Object, mètode ForEach, for, while, do-while, do-until) amb diferents implicacions de rendiment i memòria.
  • ForEach-Object -Parallel a PowerShell 7 permet processar elements del pipeline en paral·lel, controlant la concurrència mitjançant ThrottleLimit i sacrificant l'ordre de sortida.
  • Start-Job, Start-ThreadJob, runspaces i antics Workflows amb foreach -Parallel proporcionen altres models de paral·lelisme amb diferents sobrecàrregues i nivells d'aïllament.
  • Mesurar el rendiment, evitar errors típics en foreach i aplicar bones pràctiques de control de flux i maneig derrors és clau per construir pipelins potents i estables.

com veure l'historial de comandes a Powershell i CMD

Quan comences a treballar amb PowerShell, tard o d'hora et topes amb el mateix problema: tens molts objectes de processar en bucle i el rendiment es desploma. Bústies d'Exchange, fitxers d'un servidor, processos en múltiples equips, per exemple per gestionar múltiples PCs i servidors… tot passa pel pipeline i el clàssic ForEach-Object. Però, és clar, aquest bucle va element a element, de manera seqüencial, i arriba un moment en què es queda curt.

Amb l'arribada de PowerShell 7 el panorama va canviar força. Ara tenim a la nostra disposició pipelining paral·lel amb ForEach-Object -Parallel i altres enfocaments de concurrència que permeten esprémer millor els nuclis de la CPU i el temps d'espera d'operacions de xarxa o de disc. El truc està en entendre bé què fa cada opció, quan convé fer-la servir i com limitar la simultaneïtat per no carregar-nos el rendiment en lloc de millorar-lo.

Bucles a PowerShell: el punt de partida

Abans de ficar-nos de ple als pipelins paral·lels, convé tenir clar què són els bucles i com es comporten les diferents construccions de PowerShell. En essència, un bucle és una forma de repetir una o diverses instruccions múltiples vegades, ja sigui un nombre d'iteracions conegut o mentre es compleix una condició. En administració de sistemes, això és el pa de cada dia: des d'aplicar permisos a tots els usuaris d'un grup, fins a recórrer milers de fitxers en un directori o processar esdeveniments amb Get-WinEvent.

PowerShell ofereix diverses estructures de control clàssiques com for, foreach, while, do-while y do-until, a més de cmdlets i mètodes que també permeten iterar, com ForEach-Object o el mètode .ForEach() en col·leccions. Totes formen la base sobre la qual després podrem construir lògiques més avançades, incloent-hi l'execució en paral·lel.

foreach vs ForEach-Object vs mètode ForEach

A PowerShell hi ha certa confusió perquè tenim tres maneres diferents de fer “foreach” i no es comporten igual. A nivell sintàctic s'assemblen, però internament la cosa canvia força, sobretot pel que fa a l'ús de memòria i al pipeline.

Paraula clau foreach (foreach statement)

La paraula clau foreach és la construcció de bucle “clàssica” del llenguatge. Càrrega la col·lecció completa en memòria i recorre cada element seqüencialment. És còmoda i molt ràpida quan ja tens un array o una altra col·lecció en memòria i la seva mida és raonable. El processament segueix aquests passos: primer PowerShell determina quants elements hi ha, després assigna cada element, un per un, a una variable (per exemple $item) i executa el bloc de codi per a cada iteració.

Aquest enfocament és ideal quan treballes amb col·leccions en memòria ben fitades, com una llista de processos, un conjunt de rutes o un rang de números. No obstant això, no està pensat per llegir directament des del pipeline, i si la col·lecció és molt gran pot consumir força memòria.

Cmdlet ForEach-Object al pipeline

El cmdlet ForEach-Object funciona de manera diferent: està dissenyat per processar un objecte cada cop segons va arribant pel pipeline. És a dir, no cal tenir tota la col·lecció carregada des del principi. Això ho fa molt eficient per a escenaris on l'origen de dades pot generar molts elements (per exemple, Get-ChildItem sobre un arbre de carpetes enorme) o quan no vols o no pots guardar tota la col·lecció en memòria, o en fer servir eines externes com Git des de PowerShell.

A canvi, té una lleugera sobrecàrrega pel mateix mecanisme del pipeline i sol ser una mica més lent que la paraula clau foreach quan tots dos treballen sobre la mateixa col·lecció en memòria. No obstant, guanya punts en escalabilitat i memòria, cosa que en entorns de producció sol pesar més que esprémer unes mil·lèsimes de velocitat per objecte.

Mètode ForEach() en col·leccions

Des de PowerShell 5, moltes col·leccions com ara arrays i llistes exposen el mètode .ForEach()Permetre aplicar un scriptblock a cada element de manera molt concisa. Per exemple, $array.ForEach({ ... }). Aquest mètode és orientat a objectes i va molt bé quan vols encadenar operacions i treballar de forma fluida sobre col·leccions ja creades.

En termes de rendiment, el mètode .ForEach() sol ser més eficient que el cmdlet ForEach-Object quan tot ja està en memòria, perquè evites part de la mecànica del pipeline. Tot i així, comparteix la mateixa limitació bàsica: treballa sobre col·leccions que ja han estat materialitzades, per la qual cosa no és l'eina adequada per a processaments “streaming” de grans volums de dades.

Diferències pràctiques entre foreach i ForEach-Object

A la pràctica del dia a dia apareixen preguntes com: “per què aquest script EUA Get-Mailbox ... | ForEach-Object { ... } i un altre fa primer $Mailboxes = Get-Mailbox ... i després un foreach ($Mailbox in $Mailboxes)?” La diferència principal és que en el primer cas tot va a través de pipeline, processant cada bústia a mesura que arriba, mentre que al segon s'emmagatzema tota la col·lecció a la variable $Mailboxes i després es recorre.

  Com utilitzar Google Workspace CLI per automatitzar el teu Workspace

L'elecció no és només qüestió d'estil: si fas servir milers d'elements, l'ús de memòria i la manera de gestionar els errors poden variar força, i aquí el pipeline sol ser més amigable amb el consum, a costa d'un lleuger cost de rendiment per objecte.

Altres tipus de bucles: for, while, do-while, do-until

A més de l'ecosistema foreach, PowerShell inclou altres bucles tradicionals que continuen sent molt útils. El bucle for permet iterar sobre un rang de números, incrementant o decrementant un índex fins que es deixa de complir una condició. És perfecte per recórrer arrays per posició, saltar elements o accedir-hi de forma inversa.

Els bucles do-while y do-until executen el cos almenys una vegada i després comproven la condició: a do-while es repeteix mentre es compleixi, ia do-until es repeteix fins que es compleixi. Són molt útils quan necessites que el bloc s'executi sempre almenys una vegada, per exemple en demanar dades a l'usuari o en provar un recurs fins que respongui correctament.

el bucle while avalua la condició just al principi; si no es compleix, no entra al cos. És el clàssic “mentre això segueixi sent veritat” que tots coneixem d'altres llenguatges. En tasques d'automatització podeu servir per vigilar un servei, un valor numèric o l'estat d'un recurs fins a assolir un objectiu.

Control fi del flux: break, continue i return

Per controlar millor el que passa dins dels bucles tenim tres paraules clau: break, continue y return. Encara que de vegades es passen per alt, marquen força la diferència en scripts complexos.

La instrucció break permet sortir immediatament del bucle actual. És molt útil quan recorres un array o una llista buscant alguna cosa en concret: quan ho trobes, fas break i t'estalvies continuar iterant sense necessitat.

la paraula continue salta al següent cicle del bucle, ignorant la resta d'instruccions de la iteració actual. S'usa molt per “filtrar” elements: si alguna cosa no compleix una condició, fas continue i només processes de debò allò que t'interessa.

Finalment, return no només trenca el bucle, sinó que a més torna un valor des d'una funció o un script. És com dir “ja tinc el que buscava, aquí ho tens, i me'n vaig”. Usat amb cap, simplifica força la lògica dels scripts.

ForEach-Object -Parallel a PowerShell 7

Amb PowerShell 7 es va introduir una novetat molt potent: el paràmetre -Parallel del cmdlet ForEach-Object. Aquesta funcionalitat, afegida inicialment a la beta 3 de la versió 7.0, permet processar diversos elements del pipeline alhora, sense haver de recórrer a mòduls externs oa codi complex amb runspaces.

La idea és senzilla: en lloc de tractar un objecte darrere l'altre de forma seqüencial, el cmdlet pot llançar múltiples iteracions en paral·lel, cadascuna executant el bloc de script sobre un element diferent de la col·lecció. D'aquesta manera, si teniu feines independents entre si (com diverses peticions HTTP, múltiples còpies de fitxers a diferents destinacions o consultes contra moltes bústies), podeu reduir el temps total d'execució de forma significativa.

Comportament de ForEach-Object abans i després de -Parallel

Sense el paràmetre -Parallel, ForEach-Object processa els elements en ordre, un a un, esperant que cada bloc acabi abans de passar al següent. Si feu una prova simple amb 10 objectes i una petita pausa artificial, veureu que el temps total és pràcticament la suma dels 10 temps individuals.

Activant -Parallel el mateix codi es pot arribar a executar diverses vegades més ràpid, perquè es reparteixen les iteracions entre diversos subprocessos. Un exemple típic: el que triga uns 10 segons en mode seqüencial pot caure a menys de 3 segons en paral·lel, segons el nombre de tasques simultànies i el tipus de feina que facis dins del bloc.

Ordre de sortida i nombre d'operacions simultànies

Hi ha un detall important: quan uses ForEach-Object -Parallel, el ordre dels resultats ja no està garantit. Els elements es retornen a mesura que van acabant les tasques, no seguint l'ordre original d'entrada. Si la vostra lògica depèn de l'ordre, haureu de reordenar després o incloure informació d'índex per reconstruir-lo.

Per defecte, PowerShell 7 executa cinc operacions simultànies. Si necessites ajustar això, fas servir el paràmetre ThrottleLimit, que defineix quants elements com a màxim es processen alhora. Configurar-lo correctament és clau per trobar lequilibri entre velocitat i consum de recursos.

Quan no convé paral·lelitzar

No tots els problemes es resolen ficant paral·lelisme a sac. En alguns escenaris, com transferències de fitxers pesants a través d'una xarxa limitada, llançar massa operacions en paral·lel pot saturar l'amplada de banda i provocar que tot vagi, paradoxalment, més lent. El mateix passa amb APIs o serveis remots que tenen límits de peticions.

En altres casos, com a operacions que depenen estrictament del ordre d'execució o que modifiquen un mateix recurs compartit, el paral·lelisme pot introduir condicions de carrera o inconsistències. En aquests contextos sol ser millor seguir amb un enfocament seqüencial o aplicar paral·lelisme més granular i controlat.

  Ciberseguretat i accés a comptes: guia completa per protegir-te

Altres opcions per executar en paral·lel a PowerShell

A més ForEach-Object -Parallel, PowerShell ofereix diverses maneres de treballar en paral·lel, tant en versions antigues com a la branca 7.x. Cadascuna té pros i contres, i convé conèixer-les per escollir bé l'eina.

Start-Job: treballs en processos independents

El cmdlet Start-Job existeix a totes les versions de PowerShell i llança treballs en processos separats, cadascun amb la seva pròpia instància de PowerShell. Això proporciona un aïllament fort, però té molta sobrecàrrega: crear processos nous, serialitzar i deserialitzar els objectes de tornada, etc.

A molts escenaris, un bucle seqüencial pot resultar més ràpid que abusar de Start-Job, especialment quan les tasques són curtes o es tornen moltes dades complexes. A més, Start-Job no inclou de sèrie un ThrottleLimit, de manera que has de gestionar tu mateix quants treballs sexecuten de forma simultània.

Start-ThreadJob: treballs basats en subprocessos

el mòdul ThreadJob proporciona el cmdlet Start-ThreadJob, que utilitza runspaces (espais d'execució) per crear treballs més lleugers que els de Start-Job. Compartint procés, s'eviten moltes de les pèrdues d'informació de tipus que provoca la serialització entre processos.

A PowerShell 7 i versions posteriors, el mòdul ThreadJob ja ve inclòs, mentre que a Windows El PowerShell 5.1 es pot instal·lar des de la PowerShell Gallery. Start-ThreadJob suporta el paràmetre ThrottleLimit, de manera que pots controlar quants treballs d'aquest tipus s'executen alhora, evitant esgotar recursos del sistema.

Runspaces i SDK de PowerShell

Per a escenaris avançats, podeu treballar directament amb l'espai de noms System.Management.Automation.Runspaces de l'SDK de PowerShell. Això permet crear la teva pròpia lògica de paral·lelisme molt fina, controlant la gestió de runspaces, pools, sincronització, etc. De fet, tant ForEach-Object -Parallel com a Start-ThreadJob es recolzen internament en runspaces.

Aquest enfocament ofereix màxima flexibilitat i rendiment potencial, però també implica més complexitat i codi “de plumbing”. En molts casos, amb els cmdlets integrats (ForEach-Object -Parallel i Start-ThreadJob) es cobreixen la majoria de necessitats i no cal complicar-se tant.

Workflows i foreach -Parallel al Windows PowerShell

Al Windows PowerShell 5.1 hi havia la funcionalitat de Flux de treball, un tipus especial de script pensat per a tasques de llarga durada, amb capacitat de pausat, represa i execució en paral·lel. Dins un workflow es podia fer servir la construcció foreach -Parallel, que executa el bloc d'script una vegada per cada element d'una col·lecció alhora.

La sintaxi bàsica de foreach -Parallel és una mena foreach -Parallel ($item in $collection) { ... } i només és vàlida dins d'un flux de treball. Els elements de la col·lecció es processen en paral·lel, mentre que els ordres dins del bloc s'executen de forma seqüencial per cada element (tot i que també es pot combinar amb blocs parallel { ... } interns).

Els workflows, però, no estan disponibles a PowerShell 7 ni en versions posteriors, i tampoc es recomanen per a desenvolupaments nous. Tot i així, és interessant conèixer la sintaxi i el concepte si mantens scripts heretats que usen foreach -Parallel, ja que el seu comportament difereix tant del foreach normal com de ForEach-Object -Parallel.

Limitar concurrència amb ThrottleLimit

El fet que alguna cosa es pugui executar en paral·lel no significa que sigui més ràpid. De fet, aplicacions molt I/O intensives o scripts lleugers poden perdre rendiment si hi ha massa tasques simultànies, ja sigui per saturar CPU, disc, xarxa o simplement per la sobrecàrrega de gestionar tants fils alhora.

Per equilibrar aquest aspecte, Start-ThreadJob y ForEach-Object -Parallel ofereixen el paràmetre ThrottleLimit, que estableix el màxim de treballs o iteracions que es poden executar alhora. Si s'intenta llançar més treballs dels permesos, aquests es queden a la cua fins que algun finalitza i allibera buit.

Des de PowerShell 7.1, ForEach-Object -Parallel reutilitza runspaces d'un pool per defecte, i el valor de ThrottleLimit marca la mida del pool. El valor per defecte és 5, que acostuma a ser un bon punt de partida. També hi ha un modificador per crear un runspace nou per cada iteració (UseNewRunspace), però només compensa quan necessites un aïllament molt fort i acceptes més sobrecàrrega.

En canvi, el cmdlet Start-Job manca de ThrottleLimit, per tant, si no el controles manualment, pots acabar amb desenes o centenars de processos de PowerShell corrent alhora, amb l'impacte corresponent en rendiment i memòria.

Mesurar el rendiment dels diferents enfocaments

Una manera assenyada de decidir quin model de paral·lelisme utilitzar és mesurar temps d'execució reals per al vostre cas d'ús. Per això es pot utilitzar una funció com Measure-Parallel, que compara la velocitat de diversos enfocaments: Start-Job, Start-ThreadJob, ForEach-Object -Parallel y Start-Process.

Aquesta funció crea una col·lecció simulada d'entrades (per exemple noms de fitxers ZIP), genera diversos batxes o tandes de treball i, per a cada enfocament, mesura quant triga a processar una quantitat donada d'elements, amb un mida de lot configurat mitjançant BatchSize i un total de treballs JobCount. També adapta els enfocaments disponibles segons la versió de PowerShell i la presència o no de Start-ThreadJob.

A l'interior, Measure-Parallel configura un executable (com cmd.exe o sh) amb una llista d'arguments, i defineix un hashtable amb implementacions específiques per a cada aproximació. Amb Measure-Command calcula el temps total de processar tots els lots per a cada alternativa, permet veure missatges detallats amb -Verbose i, al final, torna un objecte amb el resum de temps per poder comparar-los fàcilment.

  Notícies d'ordinadors i el nou escenari del PC

Executant alguna cosa com Measure-Parallel -Approach All -BatchSize 5 -JobCount 20 -Verbose en un ordinador Windows amb PowerShell 7.5.1 s'observa que, en aquest cas concret, Start-Process aconsegueix el menor temps, Seguit de prop per ForEach-Object -Parallel y Start-ThreadJob, mentre que Start-Job resulta ser lopció més lenta a causa de la gran sobrecàrrega que implica llançar processos fills de PowerShell.

Paral·lelisme i bucles foreach “tradicionals”

Una cosa que sol passar a la pràctica és que, a mesura que migres workflows i scripts antics a PowerShell 7, comences a substituir runspaces manuals, mòduls de tercers com PoshRSJob o Workflows per les eines ja integrades, com Start-ThreadJob y ForEach-Object -Parallel. Això simplifica el codi i redueix la quantitat de dependències externes. També és habitual integrar solucions d'automatització com PowerShell DSC.

Hi ha administradors que veien que venien usant pools de runspaces manuals però van començar a patir comportaments inestables (per exemple, scripts que acabaven de forma aleatòria en entorns productius) i, davant la dificultat de depurar aquests problemes, han optat per reduir la complexitat i recolzar-se en funcionalitats paral·leles integrades que ja estan provades i suportades pel propi equip de PowerShell.

Tècniques avançades amb foreach: nidificació, filtratge i maneig d'errors

Més enllà del paral·lelisme, els bucles foreach permeten construir lògiques molt riques quan combines sentències if, nidació de bucles i maneig d'excepcions.

D'aquí a un foreach és habitual fer servir condicionals if per processar els elements de manera diferent segons les propietats. Per exemple, recórrer tots els fitxers d'un directori i només actuar sobre els que superen una mida determinada, copiar-los a una altra ubicació, comprimir-los o eliminar-los si compleixen certes regles d'antiguitat.

També pots niar bucles foreach per generar combinacions d'elements de diverses col·leccions: per exemple, llistes de paràmetres de prova, productes cartesians entre números i lletres, o recorreguts de dades bidimensionals. En aquest cas, és clau no reutilitzar el mateix nom de variable en bucles intern i extern, per evitar confusions i errors subtils.

Pel que fa al maneig d'errors, si un foreach topa amb una excepció sense tractar, és possible que l'script complet s'aturi, el que no sempre és desitjable. La forma habitual d'enfortir el bucle és embolicar la lògica interna en un bloc try { ... } catch { ... }, de manera que, si alguna cosa falla per a un element concret, podeu registrar l'error i seguir amb la resta de la col·lecció sense parar tota l'execució.

Errors freqüents i bones pràctiques amb foreach

Hi ha algunes ensopegades típiques en treballar amb bucles foreach a PowerShell. Un d'ells és utilitzar el mateix nom de variable en bucles imbricats, cosa que provoca que el valor intern “trepitgi” l'extern i generi resultats impredictibles. La solució és senzilla: utilitzar noms diferents per a cada nivell de nidificació.

Una altra fallada habitual és abusar de break sense pensar-ho bé, provocant que el bucle acabi abans de processar tota la col·lecció, quan en realitat el que es volia era simplement saltar certs elements. En molts casos continue és més apropiat, perquè permet ometre iteracions concretes sense abandonar el bucle del tot.

També cal anar amb ull amb els bucles infinits, sobretot quan es modifica la mateixa col·lecció que s'està recorrent. Si durant la iteració afegim elements al mateix array sense cap tipus de control, és fàcil acabar amb un bucle que mai no acaba. Una tècnica molt útil és iterar sobre una còpia de la col·lecció original, fent les modificacions necessàries sobre la versió “real”.

Pel que fa a millors pràctiques generals, sol ser recomanable UTILITZAR ForEach-Object per a dades que arriben per pipeline, mantenint el cos dels bucles el més llegible possible a base de funcions auxiliars, i aplicant una indentació i comentaris consistents per facilitar el manteniment de l'script amb el pas del temps.

Dominar les diferents variants de foreach i les seves opcions de paral·lelisme a PowerShell, des de ForEach-Object -Parallel y Start-ThreadJob fins als antics foreach -Parallel a Workflows, et permet dissenyar scripts molt més ràpids i robustos, triant en cada cas si us interessa prioritzar rendiment, memòria, ordre de sortida o simplicitat de codi, en lloc de limitar-vos sempre al bucle seqüencial de tota la vida.

PowerShell vs CMD: avantatges i desavantatges-0
Article relacionat:
PowerShell vs CMD: diferències, avantatges i quan fer-los servir