Aracılığıyla paylaş


Özel durumlar hakkında bilmek istediğiniz her şey

Hata işleme, kod yazma söz konusu olduğunda hayatın yalnızca bir parçasıdır. Beklenen davranış için genellikle koşulları denetleyebiliriz ve doğrulayabiliriz. Beklenmeyen durum oluştuğunda özel durum işlemeye döneriz. Diğer kişilerin kodu tarafından oluşturulan özel durumları kolayca işleyebilir veya başkalarının işlemesi için kendi özel durumlarınızı oluşturabilirsiniz.

Not

Bu makalenin özgün sürümü, @KevinMarquette tarafından yazılan blogda yer almıştır. PowerShell ekibi, bu içeriği bizimle paylaştığı için Kevin'e teşekkür ederiz. Lütfen PowerShellExplained.com'daki blogunu inceleyin.

Temel terminoloji

Bu terime geçmeden önce bazı temel terimleri ele almalıyız.

Özel durum

Özel Durum, normal hata işleme sorunu çözemezse oluşturulan bir olay gibidir. Bir sayıyı sıfıra bölmeye çalışmak veya yetersiz bellek, özel durum oluşturan bir şeye örnektir. Bazen kullandığınız kodun yazarı, bazı sorunlar oluştuğunda özel durumlar oluşturur.

Atma ve Yakalama

Bir özel durum oluştuğunda, bir özel durumun oluştuğu söylenilir. Oluşan bir özel durumu işlemek için yakalamanız gerekir. Özel durum oluşturulursa ve bir şey tarafından yakalanmazsa betik yürütülemez.

Çağrı yığını

Çağrı yığını, birbirini çağıran işlevlerin listesidir. Bir işlev çağrıldığında, yığına veya listenin en üstüne eklenir. İşlev çıktığında veya döndürdüğünde, yığından kaldırılır.

Bir özel durum oluştuğunda, bir özel durum işleyicisi tarafından yakalanması için bu çağrı yığını denetlenir.

Sonlandırıcı ve sonlandırılmayan hatalar

Özel durum genellikle sonlandırıcı bir hatadır. Oluşan bir özel durum yakalanmış veya geçerli yürütmeyi sonlandırıyor. Varsayılan olarak, tarafından Write-Error sonlandırılmayan bir hata oluşturulur ve özel durum oluşturmadan çıkış akışına bir hata ekler.

Bunu işaret ediyorum çünkü Write-Error ve diğer sonlandırılmayan hatalar tetiklenmiyor catch.

Özel durum yutma

Bu, yalnızca bunu gizlemeye yönelik bir hata yakaladığınız zamandır. Sorunları gidermeyi çok zorlaştırabileceğinden bunu dikkatli bir şekilde yapın.

Temel komut söz dizimi

PowerShell'de kullanılan temel özel durum işleme söz dizimine hızlı bir genel bakış aşağıda verilmiştir.

Throw

Kendi özel durum olayımızı oluşturmak için anahtar sözcüğüyle throw bir özel durum oluştururuz.

function Start-Something
{
    throw "Bad thing happened"
}

Bu, sonlandırıcı bir hata olan bir çalışma zamanı özel durumu oluşturur. Bir çağrı işlevinde tarafından catch işlenir veya betikten şöyle bir iletiyle çıkar.

PS> Start-Something

Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Bad thing happened:String) [], RuntimeException
    + FullyQualifiedErrorId : Bad thing happened

Yazma Hatası -ErrorAction Durdurma

Bunun varsayılan olarak sonlandırıcı bir hata oluşturmadığından bahsettim Write-Error . belirtirseniz -ErrorAction Stop, Write-Error ile catchişlenebilen bir sonlandırıcı hata oluşturur.

Write-Error -Message "Houston, we have a problem." -ErrorAction Stop

Lee Dailey'e bu şekilde kullanmayı -ErrorAction Stop hatırladığınız için teşekkür ederim.

Cmdlet -ErrorAction Stop

Herhangi bir gelişmiş işlevde veya cmdlet'te belirtirseniz -ErrorAction Stop , tüm Write-Error deyimleri yürütmeyi durduran veya tarafından catchişlenebilen sonlandırıcı hatalara dönüştürür.

Start-Something -ErrorAction Stop

ErrorAction parametresi hakkında daha fazla bilgi için bkz. about_CommonParameters. Değişken hakkında $ErrorActionPreference daha fazla bilgi için bkz . about_Preference_Variables.

Deneyin/Yakalayın

Özel durum işlemenin PowerShell'de (ve diğer birçok dilde) çalışmasının yolu, önce try kodun bir bölümünü kullanmanız ve hata oluşturması durumunda bunu yapabilirsiniz catch . Aşağıda hızlı bir örnek verilmiştir.

try
{
    Start-Something
}
catch
{
    Write-Output "Something threw an exception"
    Write-Output $_
}

try
{
    Start-Something -ErrorAction Stop
}
catch
{
    Write-Output "Something threw an exception or used Write-Error"
    Write-Output $_
}

Betik catch yalnızca sonlandırıcı bir hata olduğunda çalışır. try doğru yürütülürse, üzerinden catchatlar. değişkenini kullanarak bloktaki catch özel durum bilgilerine $_ erişebilirsiniz.

Deneyin/Son Olarak

Bazen bir hatayı işlemeniz gerekmez, ancak yine de bir özel durum oluşursa veya gerçekleşmezse yürütülecek bazı kodlar gerekir. Betik finally tam olarak bunu yapar.

Şu örneğe göz atın:

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()

Bir kaynağı her açtığınızda veya kaynağa bağlandığınızda, kaynağı kapatmanız gerekir. ExecuteNonQuery() özel durum oluşturursa, bağlantı kapatılamaz. Bir bloğun içinde try/finally aynı kod aşağıdadır.

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
    $command.Connection.Open()
    $command.ExecuteNonQuery()
}
finally
{
    $command.Connection.Close()
}

Bu örnekte, bir hata varsa bağlantı kapatılır. Hata yoksa da kapatılır. Betik finally her seferinde çalışır.

Özel durumu yakalamadığınız için çağrı yığınına yayılmaya devam eder.

Try/Catch/Finally

ve birlikte kullanmak catchfinally son derece geçerlidir. Çoğu zaman birini veya diğerini kullanırsınız, ancak her ikisini de kullandığınız senaryolar bulabilirsiniz.

$PSItem

Temel bilgileri aradan çıkardığımıza göre, biraz daha derine inebiliriz.

Bloğun catch içinde, özel durumla ilgili ayrıntıları içeren türünde ErrorRecord bir otomatik değişken ($PSItem veya $_) vardır. Bazı önemli özelliklere hızlı bir genel bakış aşağıda verilmiştir.

Bu örnekler için, içinde bu özel durumu oluşturmak için geçersiz bir yol ReadAllText kullandım.

[System.IO.File]::ReadAllText( '\\test\no\filefound.log')

PSItem.ToString()

Bu size günlükte ve genel çıktıda kullanmak için en temiz iletiyi verir. ToString() bir dizenin içine yerleştirilirse $PSItem otomatik olarak çağrılır.

catch
{
    Write-Output "Ran into an issue: $($PSItem.ToString())"
}

catch
{
    Write-Output "Ran into an issue: $PSItem"
}

$PSItem.InvocationInfo

Bu özellik, özel durumun oluşturulduğu işlev veya betik hakkında PowerShell tarafından toplanan ek bilgileri içerir. InvocationInfo Oluşturduğum örnek özel durumdan gelenler aşağıda verilmiştir.

PS> $PSItem.InvocationInfo | Format-List *

MyCommand             : Get-Resource
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 5
OffsetInLine          : 5
ScriptName            : C:\blog\throwerror.ps1
Line                  :     Get-Resource
PositionMessage       : At C:\blog\throwerror.ps1:5 char:5
                        +     Get-Resource
                        +     ~~~~~~~~~~~~
PSScriptRoot          : C:\blog
PSCommandPath         : C:\blog\throwerror.ps1
InvocationName        : Get-Resource

Buradaki önemli ayrıntılar, Line kodun ScriptNameve çağrının ScriptLineNumber nerede başladığını gösterir.

$PSItem.ScriptStackTrace

Bu özellik, özel durumun oluşturulduğu koda sizi getiren işlev çağrılarının sırasını gösterir.

PS> $PSItem.ScriptStackTrace
at Get-Resource, C:\blog\throwerror.ps1: line 13
at Start-Something, C:\blog\throwerror.ps1: line 5
at <ScriptBlock>, C:\blog\throwerror.ps1: line 18

Yalnızca aynı betikteki işlevlere çağrı yapıyorum, ancak birden çok betik söz konusuysa bu çağrıları izler.

$PSItem.Exception

Bu, oluşan gerçek özel durumdur.

$PSItem.Exception.Message

Bu, özel durumu açıklayan ve sorun giderme sırasında iyi bir başlangıç noktası olan genel iletidir. Çoğu özel durumun varsayılan iletisi vardır, ancak özel durum oluştuğunda özel bir iletiye de ayarlanabilir.

PS> $PSItem.Exception.Message

Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."

Bu aynı zamanda, üzerinde bir ayar yoksa çağrılırken $PSItem.ToString() döndürülen iletidir ErrorRecord.

$PSItem.Exception.InnerException

Özel durumlar iç özel durumlar içerebilir. Genellikle çağırdığınız kod bir özel durum yakalar ve farklı bir özel durum oluşturur. Özgün özel durum, yeni özel durumun içine yerleştirilir.

PS> $PSItem.Exception.InnerExceptionMessage
The network path was not found.

Bunu daha sonra özel durumları yeniden oluşturma hakkında konuştuğumda tekrar ziyaret ederim.

$PSItem.Exception.StackTrace

Bu, StackTrace özel durum için geçerlidir. Yukarıdakini ScriptStackTrace gösterdim, ancak bu yönetilen koda yapılan çağrılar için.

at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean
 useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs,
 String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32
 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean
 checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks,
 Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )

Bu yığın izlemesini yalnızca olay yönetilen koddan oluşturulduğunda alırsınız. Bu örnekte görebildiğimiz tek şey bu şekilde bir .NET framework işlevini doğrudan çağırıyorum. Genellikle bir yığın izlemesine baktığınızda kodunuzun nerede durup sistem çağrılarının başlayacağını ararsınız.

Özel durumlarla çalışma

Temel söz dizimi ve özel durum özelliklerinden daha fazla özel durum vardır.

Yazılan özel durumları yakalama

Yakaladığınız özel durumlar için seçmeli olabilirsiniz. Özel durumların bir türü vardır ve yakalamak istediğiniz özel durum türünü belirtebilirsiniz.

try
{
    Start-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
    Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
        Write-Output "IO error with the file: $path"
}

Özel durum türü, özel durumunuzla eşleşen bir tane bulunana kadar her catch blok için denetlenir. Özel durumların diğer özel durumlardan devralabileceğini fark etmek önemlidir. Yukarıdaki örnekte öğesinden FileNotFoundExceptionIOExceptiondevralınır. Yani eğer IOException ilk olsaydı, onun yerine çağrılırdı. Birden çok eşleşme olsa bile yalnızca bir catch bloğu çağrılır.

bir olsaydı System.IO.PathTooLongException, eşleşirdi IOException ama eğer birimiz InsufficientMemoryException olsaydı hiçbir şey onu yakalayamaz ve yığının dışına yayılırdı.

Aynı anda birden çok türü yakalama

Aynı catch deyimle birden çok özel durum türünü yakalamak mümkündür.

try
{
    Start-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
    Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
    Write-Output "IO error with the file: [$path]"
}

Bu eklemeyi önerdiğiniz /u/Sheppard_Ra için teşekkür ederiz.

Türü belirtilen özel durumlar oluşturma

PowerShell'de türü belirtilen özel durumlar oluşturabilirsiniz. Dizeyle çağırmak throw yerine:

throw "Could not find: $path"

Aşağıdaki gibi bir özel durum hızlandırıcısı kullanın:

throw [System.IO.FileNotFoundException] "Could not find: $path"

Ancak, bu şekilde yaptığınızda bir ileti belirtmeniz gerekir.

Oluşturulacak özel durumun yeni bir örneğini de oluşturabilirsiniz. Sistemde tüm yerleşik özel durumlar için varsayılan iletiler olduğundan, bunu yaptığınızda ileti isteğe bağlıdır.

throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")

PowerShell 5.0 veya üzerini kullanmıyorsanız eski New-Object yaklaşımı kullanmanız gerekir.

throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")

Yazılan bir özel durum kullanarak, siz (veya diğerleri) önceki bölümde belirtildiği gibi türüne göre özel durumu yakalayabilirsiniz.

Yazma Hatası -Özel Durum

Bu tür özel durumları 'a Write-Error ekleyebiliriz ve yine de catch özel durum türüne göre hataları ekleyebiliriz. Aşağıdaki örneklerde like kullanın Write-Error :

# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop

# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop

# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop

Write-Error -Message "Could not find path: $path" -Exception (New-Object -TypeName System.IO.FileNotFoundException) -ErrorAction Stop

Ardından şu şekilde yakalayabiliriz:

catch [System.IO.FileNotFoundException]
{
    Write-Log $PSItem.ToString()
}

.NET özel durumlarının büyük listesi

Bu gönderiyi tamamlayacak yüzlerce .NET özel durumu içeren Reddit/r/PowerShell topluluğunun yardımıyla bir ana liste derledim.

İlk olarak bu listede durumuma uygun olabilecek özel durumlar için arama yaptım. Temel System ad alanında özel durumları kullanmayı denemelisiniz.

Özel durumlar nesnelerdir

Çok fazla türlenmiş özel durum kullanmaya başlarsanız, bunların nesne olduğunu unutmayın. Farklı özel durumların farklı oluşturucuları ve özellikleri vardır. için System.IO.FileNotFoundExceptionFileNotFoundException belgelerine bakarsak bir ileti ve dosya yolu geçirebileceğimizi görürüz.

[System.IO.FileNotFoundException]::new("Could not find file", $path)

Ve bu dosya yolunu kullanıma sunan bir FileName özelliği vardır.

catch [System.IO.FileNotFoundException]
{
    Write-Output $PSItem.Exception.FileName
}

Diğer oluşturucular ve nesne özellikleri için .NET belgelerine başvurmalısınız.

Özel durumu yeniden oluşturma

Bloğunuzda catchthrow tek yapmanız gereken aynı özel durumsa, bunu yapmayın catch . Yalnızca gerçekleştiğinde bazı eylemleri işlemeyi veya gerçekleştirmeyi planladığınız bir özel durum oluşturmanız gerekir catch .

Bir özel durum üzerinde eylem gerçekleştirmek ancak aşağı akışın bununla başa çıkabilmesi için özel durumu yeniden atmak istediğiniz zamanlar vardır. Bir ileti yazabilir veya sorunu keşfettiğiniz yere yakın bir yerde günlüğe kaydedebilir, ancak sorunu yığının daha yukarısına doğru işleyebiliriz.

catch
{
    Write-Log $PSItem.ToString()
    throw $PSItem
}

İlginç bir şekilde içinden çağrısı throwcatch yapabilir ve geçerli özel durumu yeniden oluşturur.

catch
{
    Write-Log $PSItem.ToString()
    throw
}

Kaynak betik ve satır numarası gibi özgün yürütme bilgilerini korumak için özel durumu yeniden atmak istiyoruz. Bu noktada yeni bir özel durum oluşturursak, özel durumun başladığı yeri gizler.

Yeni bir özel durum yeniden oluşturma

Bir özel durum yakalarsanız ancak farklı bir özel durum oluşturmak istiyorsanız, özgün özel durumu yenisinin içine yerleştirmeniz gerekir. Bu, yığının alt kısmındaki birinin buna olarak erişmesine $PSItem.Exception.InnerExceptionolanak tanır.

catch
{
    throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}

$PSCmdlet.ThrowTerminatingError()

Ham özel durumlar için kullanma throw konusunda sevmediğim tek şey, hata iletisinin deyimini throw işaret ettiği ve sorunun bulunduğu yerin çizgi olduğunu gösterdiğidir.

Unable to find the specified file.
At line:31 char:9
+         throw [System.IO.FileNotFoundException]::new()
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], FileNotFoundException
    + FullyQualifiedErrorId : Unable to find the specified file.

31. satırda aradığım throw için betiğimin bozuk olduğunu belirten hata iletisine sahip olmak, betiğinizin kullanıcılarının görmesi için kötü bir iletidir. Onlara yararlı bir şey söylemiyor.

Dexter Dhami bunu düzeltmek için kullanabileceğimi ThrowTerminatingError() belirtmiş.

$PSCmdlet.ThrowTerminatingError(
    [System.Management.Automation.ErrorRecord]::new(
        ([System.IO.FileNotFoundException]"Could not find $Path"),
        'My.ID',
        [System.Management.Automation.ErrorCategory]::OpenError,
        $MyObject
    )
)

bunun adlı Get-Resourcebir işlevin ThrowTerminatingError() içinde çağrıldığını varsayarsak, göreceğimiz hata budur.

Get-Resource : Could not find C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework\.NETPortable\v4.6\System.IO.xml
At line:6 char:5
+     Get-Resource -Path $Path
+     ~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (:) [Get-Resource], FileNotFoundException
    + FullyQualifiedErrorId : My.ID,Get-Resource

Sorunun kaynağı olarak işlevi nasıl işaret Get-Resource ettiğinizi görüyor musunuz? Bu, kullanıcıya yararlı bir şey söyler.

bir $PSItem olduğundan ErrorRecord, yeniden atmak için bu yöntemi de kullanabiliriz ThrowTerminatingError .

catch
{
    $PSCmdlet.ThrowTerminatingError($PSItem)
}

Bu, hatanın kaynağını Cmdlet'e değiştirir ve işlevinizin iç öğelerini Cmdlet'inizin kullanıcılarından gizler.

Deneyin sonlandırıcı hatalar oluşturabilir

Kirk Munro bazı özel durumların yalnızca bir try/catch blok içinde yürütülürken hataları sonlandırdığını belirtir. Bana verdiği ve sıfıra bölme çalışma zamanı özel durumu oluşturan örnek aşağıda verilmiştir.

function Start-Something { 1/(1-1) }

Ardından hatayı oluşturup iletinin çıkışını almaya devam etmek için bu şekilde çağırın.

&{ Start-Something; Write-Output "We did it. Send Email" }

Ancak aynı kodu içine try/catchyerleştirerek başka bir şey olduğunu görürüz.

try
{
    &{ Start-Something; Write-Output "We did it. Send Email" }
}
catch
{
    Write-Output "Notify Admin to fix error and send email"
}

Hatanın sonlandırıcı bir hataya dönüştüğünü ve ilk iletinin çıkışını almadığını görüyoruz. Bu kodla ilgili sevmediğim şey, bu kodu bir işlevde kullanabileceğiniz ve birisi kullanıyorsa try/catchfarklı davranabilmesidir.

Bu konuda kendimle ilgili sorunlarla karşılaşmıyordum ama farkında olmak köşeye sıkışan bir durum.

try/catch içinde $PSCmdlet.ThrowTerminatingError()

Bir nüans $PSCmdlet.ThrowTerminatingError() , Cmdlet'inizde sonlandırıcı bir hata oluşturması ancak Cmdlet'inizden ayrıldıktan sonra sonlandırılmayan bir hataya dönüşmesidir. Bu, hatanın nasıl işleneceğini belirlemek için işlevinizin çağıranın yükünü bırakır. kullanarak veya içinden çağırarak -ErrorAction Stop bunu sonlandırıcı bir try{...}catch{...}hataya dönüştürebilirler.

Genel işlev şablonları

Kirk Munro ile yaptığım konuşmada yaptığım son bir yol, her beginprocessend birini çevreleyip tüm gelişmiş işlevlerini engellemesiydi.try{...}catch{...} Bu genel catch bloklarında, işlevlerini bırakan tüm özel durumlarla başa çıkmak için kullanan $PSCmdlet.ThrowTerminatingError($PSItem) tek bir satırı vardır.

function Start-Something
{
    [CmdletBinding()]
    param()

    process
    {
        try
        {
            ...
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

Her şey işlevlerindeki bir try deyimde olduğundan, her şey tutarlı bir şekilde hareket eder. Bu, son kullanıcıya dahili kodu oluşturulan hatadan gizleyen temiz hatalar da verir.

Tuzak

İstisnaların yönüne try/catch odaklandım. Ama bu işi bitirmeden önce bahsetmem gereken eski bir özellik var.

, trap bu kapsamda gerçekleşen tüm özel durumları yakalamak için bir betik veya işleve yerleştirilir. Bir özel durum oluştuğunda içindeki trap kod yürütülür ve normal kod devam eder. Birden çok özel durum oluşursa, tuzak tekrar tekrar çağrılır.

trap
{
    Write-Log $PSItem.ToString()
}

throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')

Kişisel olarak bu yaklaşımı hiç benimsemedim ama yönetici veya denetleyici betiklerinde özel durumları ve tüm özel durumları günlüğe kaydeden ve sonra da yürütmeye devam eden değeri görebiliyorum.

Kapanış notları

Betiklerinize uygun özel durum işleme eklemek, bunları daha kararlı hale getirmekle kalmaz, aynı zamanda bu özel durumlarla ilgili sorunları gidermenizi de kolaylaştırır.

Konuşmak throw için çok zaman harcadım çünkü özel durum işleme hakkında konuşurken temel bir kavramdır. PowerShell, kullanabileceğiniz throwtüm durumları işleyen bir Write-Error de verdi. Bu nedenle, bunu okuduktan sonra kullanmanız throw gerektiğini düşünmeyin.

Bu ayrıntıda özel durum işleme hakkında yazmaya zaman ayırdığıma göre, kodumda hata oluşturmak için kullanmaya Write-Error -Stop geçeceğim. Ayrıca Kirk'ün tavsiyesini alacağım ve her işlev için özel durum işleyicimi yapacağım ThrowTerminatingError .