Da ich inzwischen das digitale Abo der taz  habe und selbiges nicht jeden Tag manuell runterladen will oder aus dem EMailpostfach fischen, habe ich mir ein WindowsPowershell Skript dafür gebaut, welches von Windows automatisch jeden morgen als geplanter Vorgang ausgeführt wird.

Den taz_loader wollte ich nicht nehmen. Der speichert seine Einstellungen im Programmverzeichnis,was in Windows ein absolutes no-go ist. Ausserdem speichert das Passwort im Klartext. Last, but not least war das eine gute Gelegenheit, ein paar neue Powershell 2.0 Features auszuprobieren.

Hier ist das Script:

<#
.SYNOPSIS
Lädt aktuelle Ausgabe der TAZ als PDF auf den Desktop.

.DESCRIPTION
Lädt aktuelle Ausgabe der TAZ als PDF auf den Desktop.
Benutzt Benutzername und Passwort wie sie verschlüsselt im Benutzerprofil des aktuellen Windows-Benutzers
gespeichert sind.

Wenn kein Benutzername und Passwort gespeichert ist, fragt download-taz danach und speichert sie 
ab. Das Passwort wird in einer Datei verschlüsselt gespeichert, sodaß nur der Windows-Benutzer, der es 
gespeichert hat, es auf diesem Computer wieder entschlüsseln kann.

.PARAMETER Date
Datum der TAZ Ausgabe, die runtergeladen wird. Lädt die heutige Ausgabe, wenn nichts angegeben wird.

.PARAMETER NewCredentials
Gespeicherter Username und Passwort werden ignoriert, nach neuen gefragt und diese wieder abgespeichert.

.PARAMETER ShowProgress
Downloadfortschritt anzeigen. Hinweis: Funktioniert nur, wenn der Webseite die Gesamtgröße der 
Datei liefert

.PARAMETER DeleteOldIssues
Verschiebt alte Ausgaben vom Desktop in den Papierkorb

.INPUTS
Keine. Download-Taz akzeptiert keine Pipeline-Objekte 

.OUTPUTS
Keine.

.EXAMPLE
PS> download-taz
Lädt die TAZ-Ausgabe von heute als PDF auf den Desktop. 

.EXAMPLE
PS> download-taz "08/21/09"
Lädt die TAZ-Ausgabe vom 21. August 2009 als PDF auf den Desktop. 

.EXAMPLE
PS> download-taz -DeleteOldIssues 
Lädt die TAZ-Ausgabe von heute als PDF auf den Desktop und löscht alte Ausgaben.


.EXAMPLE
PS> download-taz -NewCredentials
Ignoriert gespeicherten Benutzernamen und Passwort, fragt nach neuen, speichert diese und lädt die 
heutige Ausgabe auf den Desktop.

.NOTES 
Zum Speichern von Benutzername und Passwort benutzt download-taz das Script export-pscredential 
von http://poshcode.org/?show=474

Mehr Informationen über das TAZ digital Abonnement gibt es hier: http://www.taz.de/digitaz/.digiabo
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param([DateTime]$Date=(get-date),
      [switch]$newCredentials,
      [switch]$ShowProgress=$false,
      [switch]$DeleteOldIssues=$false)

# Lade export-pscredential script, welches die Funktionen export/import-pscredential zur Verfügung stellt.
. Export-pscredential.ps1

$webClient = new-object System.Net.WebClient

try
{
    $CredentialsPath = join-path ([System.Environment]::GetFolderPath("ApplicationData")) "Download-TAZ\credentials.txt"

    $newCredentials = -not (test-path $CredentialsPath)

    if ($newCredentials)
    {
        if ($PSCmdLet.ShouldProcess("Benutzername/Password","Abfragen und speichern"))
        {
            $c = get-credential
            [System.IO.Directory]::CreateDirectory((split-path $CredentialsPath -parent))
            export-pscredential -Credential $c -Path $CredentialsPath
        }
        else
        {
            return
        }
    }
    else
    {
        $c = import-pscredential -Path $CredentialsPath
    }

    $webclient.Credentials = $c

    $ProgressAction = {
        write-progress -Activity "Download TAZ" -Status "Runterladen" -PercentComplete $Eventargs.ProgressPercentage
    }

    if ($ShowProgress)
    {
        # Lösche alte Event-Subscription bevor neuer Eventhandler registriert wird.
        get-eventsubscriber | where { $_.EventName -eq "DownloadProgressChanged"} | foreach-object { unregister-event -SubscriptionID $_.SubscriptionID }
        Register-objectevent -EventName "DownloadProgressChanged" -InputObject $webclient -Action $ProgressAction | Out-Null
    }
    
    # Da die Download-Funktion asynchron aufgerufen wird, warten wir auf das DownloadFileCompleted event
    get-eventsubscriber | where { $_.EventName -eq "DownloadFileCompleted"} | foreach-object { unregister-event -SubscriptionID $_.SubscriptionID }
    Register-objectevent -EventName "DownloadFileCompleted" -InputObject $webclient -SourceIdentifier "DownloadTAZ.Completed" | Out-Null

    $url = "http://www.taz.de/taz/abo/get.php?f={0:yyyy_MM_dd}.pdf" -f $Date

    # Zielpfad für das runtergeladene PDF
    $LocalPath = [System.Environment]::GetFolderPath("Desktop")
    $filename = "TAZ_{0:yyyy_MM_dd}.pdf" -f $Date
    $localfilename = join-path $LocalPath $filename

    if ($DeleteOldIssues)
    {
        ls (join-path $LocalPath "\*") -include "TAZ_????_??_??.pdf" -exclude $Filename | foreach-object {
            if ($PSCmdlet.ShouldProcess("$_", "In den Papierkorb verschieben"))
            {
                $shell = new-object -comobject "Shell.Application"
                $item = $shell.Namespace(0).ParseName($_.Fullname)
                $item.InvokeVerb("delete")
            }
        }
    }
    
    if (test-path $localfilename)
    {
        # Angeforderte TAZ-Ausgabe schon vorhanden. Nichts zu tun
        return
    }
    
    if ($PSCmdlet.ShouldProcess("$url", "Nach $localfilename runterladen"))
    {
        # Sicherstellen, das kein altes Event in der Wartschlange steckt
        remove-event -SourceIdentifier "DownloadTAZ.Completed" -erroraction SilentlyContinue

        # Download asynchron starten. Das erlaubt uns, auf das DownloadProgress Event zu reagieren
        $Webclient.DownloadFileAsync($url, $localfilename)
        
        # Warten bis Download fertig ist. Danach Event aus der Warteschlange löschen
        wait-event -SourceId "DownloadTAZ.Completed" | Out-Null
        remove-event -SourceId "DownloadTAZ.Completed"
        $webclient = $null
    }
}
finally
{
    # Aufräumen
    if ($webClient)
    {
        $webClient.CancelAsync()
    }
    
    get-eventsubscriber | where { $_.EventName -eq "DownloadProgressChanged"} | foreach-object { unregister-event -SubscriptionID $_.SubscriptionID -whatif:$false -confirm:$false }
    get-eventsubscriber | where { $_.EventName -eq "DownloadFileCompleted"} | foreach-object { unregister-event -SubscriptionID $_.SubscriptionID -whatif:$false -confirm:$false }
}

Leider unterstützt die Sourcecode-Anzeige von wordpress.com die Powershell-Syntax nicht, sodass die Farbdarstellung des Codes eher suboptimal  ist.

Das download-taz Script läuft nur mit Powershell v2.0. Neu war für mich in diesem Script die Verwendung von Events. Man sieht in den Zeilen 94 bis 103, wie zunächst ein Scriptblock definiert wird, der dann via register-objectevent als Eventhandler für das DownloadProgressChanged-Event des Webclient-Objekts registriert wird. Die Idee war, beim Runterladen den Fortschritt mit Hilfe des write-progress CmdLets anzuzeigen. Als ich es fertig hatte, musste ich leider feststellen, das der TAZ-Webserver die Dateigröße nicht mitliefert, die aber für die prozentuale Anzeige von write-progress nötig ist. Ich hätte mir den Aufwand also sparen können. Immerhin hab ich gelernt, wie man Powershell-Events im Zusammenhang mit .NET Objekten verwendet.

Schon öfter benutzt, aber nach wie vor sehr nützlich ist die automatisch aus den Kommentaren generierte Hilfe. Die lässt sich einfach mit

download-taz –?

oder

get-help download-taz

anzeigen.

Das Speichern von Benutzername und Passwort hatte ich erst angefangen, auf Basis der .NET SecureString-Klasse selber zu implementieren, hab dann aber genau diese Funktion hier gefunden. SecureString verschlüsselt den Inhalt sodaß nur der aktuelle Windows-Benutzer auf dem aktuell verwendeten Computer ihn entschlüsseln kann. Das so verschlüsselte Passwort wird in einer Datei gespeichert, die im Benutzerprofil-Bereich des aktuellen Windows-Benutzers liegt. Damit könnte zwar immer noch ein Schadprogramm, welches mit meinen Benutzerrechten läuft, die Datei lesen und das Passwort verschlüsseln, aber besser als ein Klartext-Passwort ist das allemal. Und wenn ein Schadprogramm auf meinem Rechner laufen kann, das ist das Digiztaz-Passwort das kleinste Problem.

Advertisements

Mit der neuen Windows Powershell Version 2.0 kann man sehr einfach die Standardparameter -whatif und -confirm in eigenen Funktionen benutzen:

001
002
003
004
005
006
007
008
009
010
function do-something
{
[CmdletBinding(supportsShouldProcess=$true)]
param($p)

    if ($pscmdlet.shouldProcess("$p", "do-something"))
    {
        write-host "do-something $p"
    }
}

Mit der Deklaration in Zeile 3 akzeptiert die Funktion automatisch –whatif und –confirm:

PS> do-something "dangerous" -whatif
What if: Performing operation "do-something" on Target "dangerous".
PS> do-something "dangerous" -confirm

Confirm
Are you sure you want to perform this action?
Performing operation "do-something" on Target "dangerous".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): y
do-something dangerous

Sehr praktisch.