$MyInvokasyon ile Daha Fazlasını Yapmak


Çok uzun zaman önce birisi bana PowerShell ile ilgili paylaştığım bir şey hakkında Twitter'da yorum yaptı. $MyInvokasyon değişkeni hakkında daha fazla bilgi edinmek istiyordu. Bu, iyi belgelenmemiş bir şeydir ancak PowerShell komut dosyası oluşturmanızda çok yararlı olabilir. Biraz daha ayrıntılı olarak bakalım.

$MyInvokasyon değişkeni, bir PowerShell betiğini veya işlevini çalıştırdığınızda otomatik olarak oluşturulur. Teknik olarak bu bir System.Management.Automation.InvokasyonInfo nesnesidir. Microsoft'un belgelerini burada bulabilirsiniz. Ancak bunu pratik bir bakış açısıyla inceleyelim.

İşte basit bir gösteri fonksiyonu.

Function Get-Foo {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Name,
        [datetime]$Since = (Get-Date).AddHours(-24)
        )

        $MyInvocation

        Write-Host "Getting $name since $since" -fore yellow
}

$MyInvation'dan en iyi şekilde yararlanmak için, betik dosyanıza nokta kaynak sağlamanın veya modülü PowerShell oturumunuza aktarmanın en iyi yol olduğunu buldum. Bu işlev $MyInvokasyonu göstermekten başka bir şey yapmaz.

Bu zengin bir nesne olmasına rağmen, muhtemelen PowerShell betiklerinizde yararlı bulacağınız yalnızca bir avuç özellik vardır. MyCommand özelliğini her zaman kullanıyorum.

MyInvokasyonum.Mycommand

İşte demo fonksiyonunun başka bir versiyonu.

Function Get-Foo {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Name, [
        datetime]$Since = (Get-Date).AddHours(-24)
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Name"

        #create a global copy for testing purposes
        $global:mi = $myinvocation

        Write-Host "Getting $name since $since" -fore yellow
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

Bu neredeyse tüm PowerShell işlevlerimin formatıdır. Begin ve End komut dosyası blokları, komut adını yansıtan Verbose ifadelerine sahiptir. Bu yaklaşımı seviyorum çünkü işlev adını değiştirirsem başka bir kodu değiştirmem gerekmiyor.

Bunun gerçekten yararlı olduğu yer, diğer işlevleri çağıran işlevlerim olduğu zamandır. Bu ayrıntılı ifadeler akışı izlemeyi kolaylaştırır.

Function Get-Foo {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Name, [
        datetime]$Since = (Get-Date).AddHours(-24)
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Name"

        #create a global copy for testing purposes
        $global:mi = $myinvocation

        Write-Host "Getting $name since $since" -fore yellow
        If (Test-Foo $name) {
            $name
        }
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

Function Test-Foo {
    [cmdletbinding()]
    Param([string]$Name)

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"

    } #begin
    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Testing $name"
        $True
    } #process
    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"

    } #end
}

Ayrıntılı çıktı artık bir komutta olduğumu, diğerini başlattığımı ve orijinal komuta döndüğümü gösteriyor. Yıllar geçtikçe bu tekniğin çok değerli olduğunu gördüm.

Kodlar

$MyInvation'ı komut dosyalarıyla da kullanabilirsiniz.

#requires -version 5.1

Param(
    [Parameter(Position = 0, Mandatory)]
    [string]$Name,
    [datetime]$Since = (Get-Date).AddHours(-24)
)
write-host "Starting $(Resolve-Path $myinvocation.InvocationName)" -ForegroundColor green
write-host "PSScriptroot is $PSScriptRoot" -ForegroundColor green
#create a global copy for testing purposes
$global:mi = $MyInvocation

Write-Host "Getting $name since $since" -fore yellow
write-host "Ending $($myinvocation.mycommand)" -ForegroundColor green

throw "I failed"

Komut dosyasının adını $MyInvokasyon'dan yakalayabilirim. Herhangi bir PSDrive'ı veya ilgili yolu tam dosya sistemi yoluna dönüştürmek için Resolve-Path'i kullanarak işleri bir adım daha ileri götürüyorum. $PSScriptRoot geçerli komutun konumunu yansıtır. Ancak işlerin ilginçleştiği yer burası.

Çağrı Bilgileri

Demo betiğim kasıtlı olarak bir istisna attı. Hadi bakalım.

Bu aynı türde bir nesnedir ancak özellikleri farklı şekilde doldurulur. Burada hatanın nerede oluştuğunu 16. satırda görebiliyorum. Hatta satır metnini bile görüyorum. Bunun olanakları var.

Hata Ayıklamayı Çağırmak

Aynı sonucu fonksiyonları kullanarak da elde edebilirsiniz. Aslında, ps1 dosyasını işlevinizle nokta kaynak olarak kullandığınızda daha da iyi olur. Bu, bir istisna nesnesinden InvokasyonInfo'dur.

Bu bana hangi işlevi çalıştırdığımı söylemiyor ancak başarısız olan işlevin içinde çalıştırmaya çalıştığım komutu, satır numarasını ve kaynak dosyayı gösteriyor!

Bu bilgiyle, kaynak dosyayı düzenlemek ve doğrudan rahatsız edici satıra atlamak için koduma kod ekleyebilirim. Hala PowerShell ISE'yi kullanıyorsanız $PSISE nesne modelini kullanarak bunu otomatikleştirebilirsiniz. Ancak PowerShell ISE bir düzenleyicidir ve komut dosyalarını otomatik olarak çalıştırmasını sağlayamazsınız. Bunun yerine, gerekli bilgi parçalarını içeren geçici bir dosya oluşturmak için yardımcı bir işlevim var.

Function New-ISETemp {
    [cmdletbinding()]
    [Outputtype("System.IO.FileInfo")]
    Param([string]$FilePath, [int]$LineNumber)

    $content = @"
#Press F5 to Run This Script

`$f = `$psise.CurrentPowerShellTab.files.add("$FilePath")
`$psise.CurrentFile.Editor.Focus()
`$LineNumber = $LineNumber
`$f.Editor.SetCaretPosition(`$LineNumber,1)
`$f.editor.SelectCaretLine()
"@

    $tmp = [system.io.path]::GetTempFileName() -replace "\.tmp$", ".ps1"
    $content | Out-File -FilePath $tmp

    #write the temp file as the function output
    $tmp
}

Fonksiyonumda, fonksiyonumla -Debug kullanırken PowerShell ISE'deki geçici dosyayı açacak kodum var.

Function Get-Foo2 {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Name, [
        datetime]$Since = (Get-Date).AddHours(-24)
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Name"
        #create a global copy of myinvocation for testing purposes
        $global:mi = $myinvocation
        Write-Host "Getting $name since $since" -fore yellow

        Try {
            Get-Service $name -ErrorAction Stop
        }
        Catch {
            Write-Warning $_.exception.message
            write-Warning $DebugPreference
            #export for testing purposes
            $_.invocationInfo | Export-Clixml d:\temp\demo-error.xml
            if ($DebugPreference -match 'continue|inquire') {
                $src = $_.invocationInfo.PSCommandPath
                $line = $_.invocationInfo.ScriptLineNumber
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Debugging line $line"
                $f = New-ISETemp -filepath $src -linenumber $line
                powershell_ise $f
            }
        }

    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

PowerShell ISE, geçici dosyam yüklenmiş olarak başlatıldı.

Tek yapmam gereken F5'e basmak, kaynak dosya yükleniyor ve rahatsız edici satır seçiliyor.

VS Code ile Hata Ayıklamayı Çağırın

Düzenleyiciniz olarak VS Code'u kullanırsanız bu daha da kolaydır. İşte fonksiyonumun VSCode versiyonu.

Function Get-Foo2 {
    [cmdletbinding()]
    Param(
        [Parameter(Position = 0, Mandatory)]
        [string]$Name, [
        datetime]$Since = (Get-Date).AddHours(-24)
    )

    Begin {
        Write-Verbose "[$((Get-Date).TimeofDay) BEGIN  ] Starting $($myinvocation.mycommand)"
    } #begin

    Process {
        Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Processing $Name"
        #create a global copy of myinvocation for testing purposes
        $global:mi = $myinvocation
        Write-Host "Getting $name since $since" -fore yellow

        Try {
            Get-Service $name -ErrorAction Stop
        }
        Catch {
            Write-Warning $_.exception.message
            write-Warning $DebugPreference
            #export for testing purposes
            $_.invocationInfo | Export-Clixml d:\temp\demo-error.xml
            if ($DebugPreference -match 'continue|inquire') {
                $src = $_.invocationInfo.PSCommandPath
                $line = $_.invocationInfo.ScriptLineNumber
                $goto = "{0}:{1}" -f $src, $line
                Write-Verbose "[$((Get-Date).TimeofDay) PROCESS] Debugging $goto"
                #launch VS Code and jump to the line
                code.cmd --goto $goto             
            }
        } #catch
    } #process

    End {
        Write-Verbose "[$((Get-Date).TimeofDay) END    ] Ending $($myinvocation.mycommand)"
    } #end
}

Artık işlevi -Debug ile çalıştırdığımda, VS Code dosyayı yüklüyor ve doğrudan satıra atlıyor.

Özet

Bu tür özyinelemeli hata ayıklamayı kodunuza dahil etmeniz gerekmeyebilir. Ancak bunun, bir InvokasyonInfo nesnesinin farklı özelliklerinden nasıl yararlanabileceğinizi gösterdiğini düşünüyorum. Yararlı bir yol bulduysanız, bunu duymayı çok isterim.

İlgili makaleler