PowerShell & Evernote: Backup erstellen

Zu meiner großen Freude habe ich zufällig eine Möglichkeit gefunden, meine Evernote-Notizbücher automatisch zu sichern. Natürlich könnte ich das auch über die Export-Funktion der Desktop-Anwendung von Evernote erledigen, aber das war mir viel zu umständlich und ich habe zu selten daran gedacht…

Also schauen wir uns zuerst einmal an, was das Evernote Kommandozeilen-tool so bietet:

Evernote Kommandozeilentool

Eine detaillierte Beschreibung der einzelnen Funktionen gibt es unter https://dev.evernote.com/doc/articles/enscript.php

Um also alle vorhandenen Evernote-Notizbücher zu exportieren, werden zuerst mit listNotebooks alle Notizbücher ausgelesen und anschließend jedes einzelne als .enex-Datei ins Backup-Verzeichnis exportiert:

# Pfad zum Evernote Kommandozeilentool
$EvernotePrg = 'C:\Program Files (x86)\Evernote\Evernote\ENScript.exe'
# Pfad zur Ablage der exportierten Dateien
$BackupPath = 'C:\Temp\EvernoteBackup'

# Liste aller Notizbücher aus Evernote auslesen
$NotebookList = & $EvernotePrg listNotebooks

foreach ($Notebook in $NotebookList) {
    "Exportiere " + $Notebook + "..."
    # Dateiname erstellen
    $BackupFileName = $BackupPath + "\" + $Notebook + ".enex"
    # Notizbuch exportieren
    & $EvernotePrg exportNotes /q "notebook:$Notebook" /f $BackupFileName
}

Jetzt noch einen ScheduledTask für das Evernote-Backup anlegen und nie wieder dran denken müssen 😉

Print Friendly, PDF & Email

Passwörter in PowerShell: einfach & sicher mit Zertifikaten

Welcher Admin hat sich nicht mindestens einmal die Frage gestellt, wie er seine Passwörter sicher abspeichern kann, um sie dann möglichst ohne viel Aufwand in seinem PowerShell Script verwenden zu können?
Und am besten soll das Script dann auch auf verschiedenen Systemen oder von allen Teammitgliedern ausgeführt werden können.
Ich habe viele verschiedene Methoden ausprobiert und war mit keiner richtig glücklich.

Bei der Variante mit

$SicheresPasswort = Read-Host -AsSecureString
$VerschluesseltesPassword = ConvertFrom-SecureString -SecureString $SicheresPasswort
Set-Content -Path "password.txt" -Value $VerschluesseltesPassword

hast Du zwar das Passwort sicher auf Deiner Festplatte abgespeichert, Du kannst aber dann das Passwort nur mit DEM Benutzer und auf DEM System, wo es erzeugt wurde, wieder entschlüsseln – für Teamarbeit gänzlich ungeeignet.

Auch das Verschlüsseln mit einem Key (wie z.B. die Seriennummer der Festplatte oder ähnlichen) ist ungünstig: entweder Du musst den Schlüssel wieder irgendwo hinterlegen werden oder Du kannst das Passwort wiederum nur auf dem System entschlüsseln, wo Du es erzeugt hast.

Auf die Lösung kam ich dann beim Lesen von Dr. Tobias Weltner’s Buch “PowerShell 5: Windows-Automation für Einsteiger und Profis” im Abschnitt “Scripte digital signieren”: diese Technik lässt sich auch wunderbar für Passwörter verwenden.

Zertifikat erstellen

Seit Windows 10 / Server 2016 verfügt das Cmdlet New-SelfSignedCertificate über erweiterte Optionen, über die z.B. auch die Gültigkeitsdauer angepasst werden kann.

Erstellen wir also zuerst ein selbstsigniertes Zertifikat:

#requires -Version 5

function New-SelfSignedCertificateForPasswords {
  <#
.SYNOPSIS
    Selbst signiertes Zertifikat erstellen und exportieren
.DESCRIPTION
    erstellt ein selbstsigniertes Zertifikat und exportiert den öffentlichen / privaten Teil in den angegebenen Pfad
.PARAMETER Password
    Passwort, mit dem der private Teil geschützt wird
.PARAMETER FriendlyName
    Anzeigename
.PARAMETER CN
    Common Name, allgemeiner Name
    wird als Dateiname verwendet
.PARAMETER OutputPath
    Zielverzeichnis, in dem die Dateien abgespeichert werden
.PARAMETER ValidUntil
    Gültigkeitsdauer (Standard = 1 Jahr)
.EXAMPLE
    New-SelfSignedCertificateForPasswords
    Erstellt ein selbstsigniertes Zertifikat im Ordner 'C:\Temp\MeinZertifikat', welches 5 Jahre gültig ist
.NOTES
    Die Datei *.cer enthält den öffentlichen Teil des Zertifikats. Diese wird zur Verschlüsselung
        der Passwörter verwendet
    Die Datei *.pfx enthält den privaten Teil des Zertifikats. Diese muss auf jedem System,
      welches die verschlüsselten Passwörter nutzen will, installiert werden
#>

    param (
        [SecureString][Parameter(Mandatory = $true)] $Password,
        $FriendlyName = 'Mein eigenes Zertifikat',
        $CN = 'MeinEigenesZertifikat',
        $OutputPath = 'C:\Temp\MeinZertifikat',
        $ValidUntil = (Get-Date).AddYears(5)
    )
  
    # Zertifikat im UserStore ablegen
    $cert = New-SelfSignedCertificate -KeyUsage DataEncipherment, KeyEncipherment -KeySpec KeyExchange -FriendlyName $FriendlyName -Subject "CN=$CN" -KeyExportPolicy ExportableEncrypted -CertStoreLocation Cert:\CurrentUser\My -NotAfter $ValidUntil -TextExtension @('2.5.29.37={text}1.3.6.1.4.1.311.80.1')

    # öffentlichen Teil als cert-Datei exportieren
    if (!(Test-Path -Path $OutputPath)) {
      $null = New-Item -Path $OutputPath -ItemType Directory -Force
    }
    $pathCer = Join-Path -Path $OutputPath -ChildPath "$CN.cer"
    $null = Export-Certificate -Type CERT -FilePath $pathCer -Cert $cert -Force

    # privaten Teil als pfx-Datei exportieren
    $pathPfx = Join-Path -Path $OutputPath -ChildPath "$CN.pfx"
    $null = $cert | Export-PfxCertificate -Password $Password -FilePath $pathPfx

    # Zertifikat aus dem Zertifikat-Speicher löschen
    $cert | Remove-Item
    Get-Item -Path Cert:\CurrentUser\CA\$($cert.Thumbprint) | Remove-Item

    # Ergebnis anzeigen
    explorer $OutputPath
}

Als Ergebnis erhalten wir zwei Dateien – den öffentlichen und privaten Teil des Zertifikats:

Zertifikatsdateien

Jetzt kannst Du den privaten Teil des Zertifikats auf dem System (bzw. den Systemen der Kollegen), auf dem Du die Passwörter entschlüsseln möchtest, installieren. Am einfachsten geht das mit einem Doppelklick auf die *.pxf-Datei.
Im Zertifikatimport-Assistent kannst Du alle Einstellungen beibehalten. Nach Eingabe des zuvor gewählten Passworts und Abschluss des Assistenten findest Du das Zertifikat bei den Benutzerzertifikaten: 

Get-ChildItem -Path Cert:\CurrentUser\My | Select Subject, NotAfter

Passwort verschlüsseln

Nun kannst Du das Passwort mit dem öffentlichen Teil des Zertifikats verschlüsseln und abspeichern:

# ---------------------------------------------*
# Text mit öffentlichem Schlüssel verschlüsseln
# ---------------------------------------------*
$geheim = Protect-CmsMessage -Content 'TopSecret123' -To 'C:\Temp\MeinZertifikat\MeinEigenesZertifikat.cer'
$geheim | Set-Content -Path 'C:\Temp\MeinZertifikat\Cred_xxx.txt'

Passwort entschlüsseln

Anschließend kannst Du das Passwort ganz einfach durch Dein Script auslesen und verwenden:
Die PowerShell ist so schlau und erkennt das zur Entschlüsselung notwendige Zertifikat automatisch.
So kannst Du auch mehrere Zertifikate zum Beispiel für unterschiedliche Bereiche und Anforderungen erstellen und diese dann nur an den berechtigten Personenkreis weitergeben.

# ---------------------------------------------*
# Text mit privatem Schlüssel entschlüsseln
# verwendetes Zertifikat ist in verschlüsseltem Text vermerkt
# ---------------------------------------------*
$Password = (Get-Content -Path 'C:\Temp\MeinZertifikat\Cred_xxx.txt') | Unprotect-CmsMessage
Write-Host $Password

Wenn Du nun die verschlüsselten Passwort-Dateien auf einem freigegebenen Laufwerk ablegst, kannst Du bzw. Deine Kollegen von verschiedenen Systemen darauf zugreifen, ohne dass Ihr bei der Ausführung des Scriptes ein Passwort eingeben müsst.
Vorausgesetzt natürlich, der ausführende Benutzer hat das entsprechende Zertifikat vorab installiert.

Print Friendly, PDF & Email

PowerShell & OneNote: Seiten (Vorlagen) kopieren

Neulich erhielt ich von einem Leser eine sehr interessante Anfrage zum Thema ‘Seite aus Vorlage erstellen’.
Da ich bisher noch keinen Weg gefunden habe, eine “OneNote Seitenvorlage” als neue Seite einzufügen, verwende ich folgenden Workaround:
Zuerst lege ich einen neuen Abschnitt “Vorlagen” an, um dort eine Seite als Vorlage mit dem gewünschten Inhalt zu erstellen. Der Inhalt dieser Seite lässt sich dann in die neu erstellte Seite kopieren.

Vorlagen-Abschnitt
# div. Vorbereitungen
$NotebookName = 'MeinNotizbuch'
$TemplateSectionName = 'Vorlagen'
$TemplatePageTitle = 'Vorlage Tagesplaner'

$TargetSectionName = 'MeineNotizen'
$TargetPageTitle = 'Seite aus Vorlage erstellt'

# OneNote Struktur einlesen und Vorlage suchen
[void][reflection.assembly]::LoadWithPartialName("Microsoft.Office.Interop.Onenote")
$OneNote = New-Object Microsoft.Office.Interop.Onenote.ApplicationClass

[Xml]$ONXml = $Null
$Onenote.GetHierarchy($Null, [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages, [ref]$ONXml)

$MyNotebook = $ONXml.Notebooks.Notebook | where name -eq $NoteBookName
$MyTemplateSection = $MyNotebook.Section | where name -eq $TemplateSectionName
$MyTemplatePage = $MyTemplateSection.Page | where name -eq $TemplatePageTitle

[ref]$TemplatePageXML = ''
$OneNote.GetPageContent($MyTemplatePage.ID,[ref]$TemplatePageXML,[Microsoft.Office.Interop.OneNote.PageInfo]::piAll)
$null = [Reflection.Assembly]::LoadWithPartialName('System.Xml.Linq')

# neue Seite erstellen
$MyTargetSection = $MyNotebook.Section | where name -eq $TargetSectionName
[ref]$newpageID = ''
$OneNote.CreateNewPage($MyTargetSection.ID,[ref]$newpageID,[Microsoft.Office.Interop.OneNote.NewPageStyle]::npsBlankPageWithTitle)

# Inhalt der Vorlage auslesen und kopieren
[String]$NewContent = $TemplatePageXML.Value
$NewContent = $NewContent.Replace($MyTemplatePage.ID, $newpageID.Value)

$null = [Reflection.Assembly]::LoadWithPartialName('System.Xml.Linq')
$NewDoc = [System.Xml.Linq.XDocument]::Parse($NewContent)
$Nodes = $NewDoc.Descendants()

# da jedes Objekt eine eigene ID hat, muss diese vor dem einfügen entfernt werden
foreach ($Node in $Nodes) {
  if ($Node.Attribute("objectID") -ne $null) {
    $Node.Attributes("objectID").Remove()
  }
}

# neuen Notiztitel setzen
[string]$NewContent = $NewDoc.ToString()
$NewContent = $NewContent.Replace($TemplatePageTitle,$TargetPageTitle)

# neu erstellte Seite updaten
$onenote.UpdatePageContent($NewContent)
Notizen Abschnitt mit der kopierten Seite
Print Friendly, PDF & Email

PowerShell & OneNote: ein Tagebuch erstellen

Ich nutze für meine täglichen Notizen ein “OneNote-Tagebuch”, welches als Notizbuch-Name das jeweilige Jahr trägt. Dieses hat 12 Abschnitte – für jeden Monat einen – und jeweils pro (Arbeits-)Tag eine Seite:

OneNote-Tagebuch
OneNote Tagebuch

Da es überhaupt keinen Spaß macht, so ein Tagebuch jedes Jahr manuell anzulegen, habe ich mir folgendes PowerShell-Script dafür gebastelt:

#requires -Version 3.0

$Year = '2017'
$NotebookPath = "https://d.docs.live.net/0123456789abcdef/Dokumente/$($Year)/"

# Notizbuch alternativ lokal speichern
#$NotebookPath = "C:\Temp\$($Year)"

# neues Notizbuch erstellen
$OneNote = New-Object -ComObject OneNote.Application
$Scope = [Microsoft.Office.Interop.OneNote.HierarchyScope]::hsNotebooks
[ref]$xml = ""
$OneNote.OpenHierarchy($NotebookPath, "", $xml, "cftNotebook")

# für jeden Monat einen neuen Abschnitt anlegen
1..12 | % {
    $Month = "{0:00}" -f $_
    $SectionPath = $NotebookPath + $Month + '.one'

    # neuen Abschnitt erstellen
    [ref]$xmlSection = ""
    $OneNote.OpenHierarchy($SectionPath, "", $xmlSection, "cftSection")

    # für jeden Arbeitstag (Montag - Freitag) im jeweiligen Monatsabschnitt eine Seite erstellen
    1..([DateTime]::DaysInMonth($Year, $Month)) | % {
        $Day = Get-Date -Year $Year -Month $Month -Day $PSItem -Hour 0 -Minute 0 -Second 0
        $Tag = $Day.Date.Day.ToString('D2')
        $Jahr = $Year.Substring(2, 2)
        $SiteName = "$($Tag).$($Month).$($Year) "

        Switch ($Day.DayOfWeek) {
            { $_ -eq 'Monday' } { $SiteName += 'Mo' }
            { $_ -eq 'Tuesday' } { $SiteName += 'Di' }
            { $_ -eq 'Wednesday' } { $SiteName += 'Mi' }
            { $_ -eq 'Thursday' } { $SiteName += 'Do' }
            { $_ -eq 'Friday' } { $SiteName += 'Fr' }
            default { $SiteName = '' }
        }

        if ($SiteName) {
            Write-Host $SiteName

            [ref]$newpageID = ''
            $OneNote.CreateNewPage($xmlSection.Value, [ref]$newpageID, [Microsoft.Office.Interop.OneNote.NewPageStyle]::npsBlankPageWithTitle)

            [ref]$NewPageXML = ''
            $OneNote.GetPageContent($newpageID.Value, [ref]$NewPageXML, [Microsoft.Office.Interop.OneNote.PageInfo]::piAll)

            $null = [Reflection.Assembly]::LoadWithPartialName('System.Xml.Linq')
            $xDoc = [System.Xml.Linq.XDocument]::Parse($NewPageXML.Value)

            $title = $xDoc.Descendants() | Where-Object -Property Name -Like -Value '*}T'

            if (-not $title)
                { throw 'Fehler: kann Titel der neuen OneNote Seite nicht auslesen' }

            # Seitentitel anpassen
            $title.Value = "$SiteName"
            Write-Verbose -Message "Seitentitel ändern"
            $onenote.UpdatePageContent($xDoc.ToString())
        }
    }
}

So kann ich auch Jahre später noch nachvollziehen, wann ich was gemacht habe oder an welchem Datum es welches Problem mit einem bestimmten System gab…

Print Friendly, PDF & Email