Pratik PowerShell: Akış Kontrolü


“Kontrol, Kontrol, Kontrolü Öğrenmelisin.” Makalelerde Yoda'nın bilgeliğinden nadiren alıntı yapıyorum ama bu bir istisna. İkinci Pratik PowerShell makalesinde gelişmiş işlevleri tartıştıktan sonra, uzmanlaşılması gereken diğer önemli komut dosyası yazma becerisi, akış kontrolü cmdlet'lerini kullanmaktır. Bu cmdlet'ler yalnızca teorik kavramlar değildir; PowerShell betiklerinizi önemli ölçüde geliştirebilecek pratik varlıklardır.

Akış kontrolü kavramı yeni değil; hesaplama sırasını kontrol etmek için elektrikli bileşenlere sahip devre kartlarının kullanıldığı ENIAC (1945) günlerinden beri programlamada mevcuttur.

Bu kavramlar bugün çoğu programlama dilinde ve kabuk ortamında hala mevcuttur. Ancak her dilin akışı yönetme yöntemi vardır ve PowerShell de bir istisna değildir. Bu makalede döngüyle ilgili PowerShell'de bulunan kod akışı yapılarını ve bunların nasıl kullanılacağını tartışacağım. Bir sonraki yazımda dallanma konusunu ele alacağım. Bu arada, bir veya iki ipucu daha verebilirim, o yüzden okumaya devam edin.

Döngü

Bir dizinin veya bir nesne koleksiyonunun öğeleri arasında yineleme yapmak, bir koşul karşılanana veya koşul artık geçerli olmayana kadar kodu tekrarlamak için bir döngü kullanabilirsiniz. Hepsini işleyene kadar dizilerdeki ve koleksiyonlardaki öğeleri yineleyebilirsiniz. Her döngü seçeneği için örneklerde, posta kutularındaki ortalama öğe sayısını hesaplama örneğini kullanacağım.

For-Loop

For, koşul karşılanana kadar kodu yürütmeye devam eden döngü türüdür. Koşulları tanımlamak için bir başlatma ifadesi, kodun yürütülmesini durduracak bir koşul ve bir adım oluşturabilirsiniz. Step ifadesi, kodun çalıştırılmasından sonra her yinelemede çalıştırılır ve genellikle bir sayacı artırarak döngünün bittiğinde sona ermesini sağlar.

Bir dizi kullanıcı nesnesi arasında gezinmek için For kullanımına bir örnek:

$TotalItems= 0
[array]$Users = Get-User -RecipientTypeDetails UserMailbox
For( $i=0; $i -lt $Users.count; $i++) {
    $TotalItems+= (Get-ExoMailboxStatistics -Identity $Users[ $i].externalDirectoryObjectId).ItemCount
}
$Avg= $TotalItems / $Users.Count
Write-Host ('Avg. Itemcount: {0}' -f $Avg)

[array] öğesinin Get-User cmdlet'ini çalıştırmanın sonucunu bir diziye aktardığını unutmayın. Get-User işleminin sonucu, kullanıcı nesnelerinin bir koleksiyonu veya tek bir kullanıcı nesnesi olabilir. Sonucun yayınlanması, bu farklılıkların ele alınacağını öngörmek için kod ekleme zorunluluğunu ortadan kaldırır. Örneğin, Get-User tek bir nesneyle sonuçlandığında, onun özelliklerine doğrudan başvurabilirsiniz, ör. $User.Identity. Ancak, arw birden çok nesne döndürüldüğünde, bu, döndürülen kullanıcı nesnelerinin tüm kimliklerinin bir dizisini oluşturur. Döküm hakkında daha fazlasını buradan okuyun.

Örnekte -format operatörünü veya kısaca -f'yi kullanıyorum. Bu, değişken referanslarını dizeden kaldırmanıza olanak tanıyarak okunabilirliği artırır. Biçimlendirilmiş bir dize kullanmak için, metinde {0} ile başlayan numaralı işaretçileri kullanın, karşılık gelen değişkeni veya metni, ilk iletilen öğenin {0} olacağı karşılık gelen konumdaki biçim işlecinden sonra geçirin ve bu şekilde devam edin. Sayı yaslama gibi format operatörüyle çok daha fazlasını yapabilirsiniz. Biçim operatörü hakkında daha fazla bilgiyi burada bulabilirsiniz.

ForEach-Object ve ForEach

ForEach, kafa karıştırıcı olabilecek iki farklı komuta atıfta bulunabilir. Bunlardan ilki, ForEach-Object'in (veya %) takma adı olan ForEach'tir. Genellikle bir dizi veya nesne koleksiyonu olan iletilen nesnelerden geçerek boru hattı girişi üzerinde çalışır. ForEach-Object her nesneden geçerken, her nesne bir { code } komut dosyası bloğu tarafından işlenir; burada nesnenin kendisine $_ değişkeni tarafından başvurulabilir. Özellikle bir cmdlet'in ardışık düzen girişini kabul etmemesi veya gerekli parametrelerin ardışık düzen üzerinden iletilmemesi durumunda, bir işlem hattından geçirilen nesneleri işlemek için ForEach-Object'i zaten kullanabilirsiniz. Örneğin:

$TotalItems= 0
$Users= 0
Get-User -RecipientTypeDetails UserMailbox | ForEach-Object {
    $TotalItems+= (Get-ExoMailboxStatistics -Identity $_.externalDirectoryObjectId).ItemCount
    $Users++
}
$Avg= $TotalItems / $Users
Write-Host ('Avg. Itemcount: {0}' -f $Avg)

Posta kutusu sayısını önceden bilmediğimizden, ortalamayı hesaplamak amacıyla sayıyı takip etmek için ek bir değişken kullandığımızı unutmayın.

Diğer ForEach, For'a benzer ve numaralandırılabilir bir nesne üzerinde yineleme yapmak için kullanılabilir. Başka bir deyişle, bir dizinin veya nesne koleksiyonunun her öğesi. ForEach dizi veya koleksiyondan geçerken değişken adını geçerli nesne olacak şekilde tanımlayabilirsiniz, örneğin:

$TotalItems= 0
$Users= Get-User -RecipientTypeDetails UserMailbox
ForEach( $User in $Users) {
    $TotalItems+= (Get-ExoMailboxStatistics -Identity $User.externalDirectoryObjectId).ItemCount
}
$Avg= $TotalItems / $Users.Count
Write-Host ('Avg. Itemcount: {0}' -f $Avg)

$Users'ı bir diziye atamadığımı unutmayın, çünkü ForEach, koleksiyonun tek veya birden fazla nesne olmasına bakılmaksızın sonuçları düzgün bir şekilde işleyecektir.

ForEach'i bir dizi veya koleksiyonun yöntemi olarak da kullanabilirsiniz, örneğin:

$TotalItems= 0
$Users= Get-User -RecipientTypeDetails UserMailbox $Users.ForEach{
    $TotalItems+= (Get-ExoMailboxStatistics -Identity $_.externalDirectoryObjectId).ItemCount
}
$Avg= $TotalItems / $Users.Count
Write-Host ('Avg. Itemcount: {0}' -f $Avg)

İşlevsel farklılıkların yanı sıra ForEach seçeneklerinin performansı da farklılık gösterebilir. ForEach yineleme öncesinde koleksiyonun tamamını yüklerken ForEach-Object, nesneleri getirilirken göndermek için işlem hattını kullanır. İki komutun performansı koleksiyonunuza ve hangi işlemin gerçekleştiğine bağlıdır. Bellek önemliyse ForEach-Object tercih edilen seçimdir. Okunabilirlik açısından ForEach yapısının kullanılması tercih edilebilir.

ForEach-Nesne -Paralel

PowerShell 7'de ForEach-Object'e -Parallel anahtarı ekleme seçeneği mevcuttur;

$Users | ForEach-Object -Parallel {
    Get-ExoMailboxStatistics -Identity $_.id
}

Paralelleştirme performans avantajı anlamına gelse de paylaşılan bir hizmet kullandığınızı unutmayın. Paralelleştirme, hizmetin Graph'ta veya diğer iş yüklerinden birinde istekleri aynı anda tetiklemesi nedeniyle kodunuzu daha hızlı kısıtlamasına neden olabilir. Ayrıca paralel çalışma için varsayılan değer 5'tir (ThrottleLimit parametresi kullanılarak yapılandırılabilir). Her paralel çalışan kod parçası, çağıranın tüm işlevlerini ve yüklü modüllerini içeren ayrı bir bağlam olan kendi çalışma alanında çalışır. Sadece arayan kişiden gelen değişkenlere $using kullanılarak önek eklenerek açıkça başvurulması gerektiği unutulmamalıdır;

$Props= 'TotalItemSize', 'TotalDeletedItemSize'
$Users | ForEach-Object -Parallel {
    Get-ExoMailboxStatistics -Identity $_.id -Properties $using:Props
}

Bu kodu çalıştıran çalışma alanlarının oluşturulması ve imha edilmesi belirli bir ek yük gerektirir; kodunuz daha fazla işlem odaklıysa bu, genel performansa zarar verebilir. Kodunuz Microsoft 365 ile konuşuyorsa, yalnızca hizmetle etkileşim kurmanın getirdiği ek yüke kıyasla ek yük ihmal edilebilir düzeyde olabilir.

Son olarak, boru hattı üzerinden gönderilen nesneler normalde sıralı bir şekilde işlenir. -Parallel kullanıldığında sıra belirlenmez. Bu nedenle ForEach-Object'i Paralel anahtarla kullanmak bazı faydalar sağlayabilir, ancak kilometreniz değişebileceğinden test yapmanız önerilir.

Sırasında

While döngüsü, koşul karşılandığında kodu işleyen ve koşul karşılanmayana kadar kodu tekrarlamaya devam eden koşullu bir döngüdür. Koşula bağlı olarak bir while döngüsünün mutlaka kodu yürütmesi gerekmez.

Practical 365 kısa süre önce Graph'tan sayfalandırılmış sonuçların nasıl getirileceğine ilişkin bu örnekte While kullanımının bir örneğini gösterdi:

$Teams.Value.ForEach( {
   $TeamsHash.Add($_.Id, $_.DisplayName) } )
   $NextLink = $Teams.'@Odata.NextLink'
      While ($NextLink -ne $Null) {
      $Teams = Invoke-WebRequest -Method GET -Uri $NextLink -ContentType $ContentType -Headers $Headers | ConvertFrom-Json
      $Teams.Value.ForEach( {
         $TeamsHash.Add($_.Id, $_.DisplayName) } )
   $NextLink = $Teams.'@odata.NextLink'
}

Burada, bir Graph API isteği tarafından döndürülen NextLink değerinin değeri alınır ve bir değer içeriyorsa (null değil), kod NextLink kalmayana kadar yinelenir. Örnekte de görülebileceği gibi, While için ortak bir kalıp, while'dan önce (kodun ilk çalışmasını tetiklemek için) ve döngü komut dosyası bloğunun altında belirtilen koşulla ilgili değerlendirmedir (bu durumda ). $NextLink=$Teams.'@odata.NextLink'.

Bir uyarı. While gibi döngülerle çalışırken sonsuz döngülerden kaçınmak için koşulun bir noktada eşleştiğinden emin olun. Aynı şey Do-While ve Do-Until için de geçerlidir. Örneğin, aşağıdaki kodda i indeksinin arttırılması yanlışlıkla atlanır, bu da döngünün hiçbir zaman sonlandırılmamasına neden olur:

[array]$Users = Get-User -RecipientTypeDetails UserMailbox
$i=0
While( $i -lt $Users.count) {
    Get-ExoMailboxStatistics -Identity $Users[ $i].externalDirectoryObjectId).ItemCount
}

Do-While, While'ın aksine kodu en az bir kez çalıştıran ve koşul karşılandığı sürece kodu tekrarlamaya devam eden koşullu bir döngüdür. Kodun yürütülebileceğinden emin olduğunuzda kullanılır. Bu örnek için, PowerShell Graph SDK'yı kullanarak bir konuk kullanıcı oluşturduğunuzu ve komut dosyasının, Exchange Online ile ilgili belirli özellikleri değiştirebilmeden önce sağlama işleminin tamamlanmasını beklediğine dair görsel bir ipucu göstermeniz gerektiğini varsayalım;

$Alias= 'philip_contoso.com#ext'
$UPN= '{0}@contoso.onmicrosoft.com' -f $Alias
$Props= @{
  DisplayName= 'Philip'
  Mail= 'philip@contoso.com'
  MailNickname= $Alias
  UserPrincipalName= $UPN
  UserType= 'Guest'
}
$Account = New-MgUser @Props

Do {
  Write-Host ('Waiting for Guest provisioning ..')
  Start-Sleep 60
} While ( $null -eq (Get-MailUser -Identity $UPN -ErrorAction SilentlyContinue))

Set-MailUser -Identity $UPN -HiddenFromAddressListEnabled:$false

Do-Until

Do-Until, kodu en az bir kez döngüye sokan ve çalıştıran ve koşul karşılanana kadar tekrar etmeye devam eden koşullu bir döngüdür. Do-While'dan farkı, koşul karşılandığı sürece Do-While'ın kodu döngüye almasıdır. Her ikisi de diğeri gibi yeniden düzenlenebilir, bu da bence çift olumsuzluklardan kaçınmaya çalışma ve okunabilirliği artırma meselesi haline geliyor.

Çift negatifin bir örneği şöyle olabilir:

Do { .. } Until ( -not ( $number -lt 10))

Bu, sayı 10'dan az olmayana kadar kodu döngüye sokar.

Bu şu şekilde yeniden yazılabilir:

Do { .. } Until ( $number -ge 10)

veya

Do { .. } While ( $number -lt 10)

Yineleme için Döngüler

Akış kontrolü hakkında konuşmak temel programlamaya değiniyor. Döngü bunun bir parçası. Bu yapıları ve hangi programlamanın ne zaman uygun olduğunu bilmek, kullanıcı veya posta kutusu nesneleri arasında okunabilirliği korurken verimli bir şekilde yineleme yapmanıza yardımcı olur.

Sorularınız veya yorumlarınız varsa yorumlardan ulaşmaktan çekinmeyin. Değilse, bir sonraki makaleye kadar akış kontrolünde başka bir yapı türü olan dallanmadan bahsedeceğim.

İlgili makaleler