Archive for the ‘Powershell’ Category

Nuget-Pakete direkt in Powershell benutzen

19 November 2016

Powershell und Nuget, ein schwieriges Thema wenn man mit diesen beiden Begriffen auf Google losgeht. Man findet massenhaft Treffer aber die meisten handeln um Powershell+Nuget+Visual Studio. In diesem Artikel möchte ich beschreiben, was man tun muss um Nuget-Pakete, die üblicherweise für C# bzw. das .Net-Framework erstellt wurden, in Powershell nutzen zu können. Konkret geht es darum Pakete die unter https://www.nuget.org/packages zu finden sind direkt unter Powershell nutzbar zu machen.

Hat man ein aktuelles Windows 10 System mit Powershell 5.1 dann bekommt man automatisch das PackageManagement-Modul mitgeliefert. Allerdings hat man zu Anfang nur Zugriff auf PSGallery. Damit man auf die große Welt der Nuget-Pakete zugreifen kann, muss man einen PacketSource für Nuget hinzufügen. Dazu benötigt man den Einsprungspunkt für die Nuget-Paketauflistung. Dieser ist direkt unter https://www.nuget.org/ zu finden.

Dort sind momentan die beiden Feeds

NuGet feed v3 (VS 2015 / NuGet v3.x): https://api.nuget.org/v3/index.json

NuGet feed v2 (VS 2013 and earlier / NuGet 2.x): https://www.nuget.org/api/v2

aufgeführt. Welchen von beiden soll man verwenden? Man kann sich an der Nuget-Version, welche Powershell verwendet orientieren. Dazu fragt man einfach

Get-PackageProvider –Name Nuget

ab. Dort wird momentan Version 2.8.5.208 geliefert. Also verwendet man den v2 Feed. Dies geschieht durch hinzufügen eines PacketSource mit der v2-Feed-Adresse:

Register-PackageSource –Name MyNuget  –Location "https://www.nuget.org/api/v2" –Providername Nuget

Beim ersten Aufruf erfolgt eine Rückfrage:

Der Anbieter "nuget v2.8.5.208" ist nicht installiert.
"nuget" kann manuell von https://oneget.org/Microsoft.PackageManagement.NuGetProvider-2.8.5.208.dll heruntergeladen und installiert werden.
Soll PackageMangement automatisch heruntergeladen und "nuget" jetzt installiert werden?

Diese sollte man mit J beantworten. Anschließend kann man auf die Nugetpaketsammlung zugreifen:

Find-Package –Source MyNuget –Name *Mail*

Damit bekommt man alle vorhandenen Pakete die im Namen Mail enthalten aufgelistet.

IP-Adresse von WSD-Drucker per Powershell ermitteln

9 November 2016

Offensichtlich gibt es keine einfache Methode die IP-Adresse eines WSD-Druckers per Eingabeaufforderung zu ermitteln. Per GUI ist es klar, dass man in der Systemsteuerung bei Geräte und Drucker bei den Eigenschaften (nicht Druckereigenschaften) fündig wird. Leider wird bei Windows 10 in der Modernen Oberfläche aber auch diese Sache ausgespart. Wäre es nicht schön so eine wichtige Information direkt per Commandline zu bekommen?

Zunächst brauchen wir einen Drucker,  jeder sollte sich hier seinen eigenen WSD-fähigen Drucker aussuchen:

$printer=Get-Printer|out-gridview –passthru

Die weitere Beschreibung macht aber nur Sinn, wenn man einen WSD-fähigen Drucker hat. Wobei alle modernen Drucker mit Netz- oder WLAN-Anschluss dies unterstützten. Es funktioniert nicht, wenn der Drucker per USB angeschlossen ist!

Nun brauchen wir vom Drucker den Druckerport über den die Kommunikation läuft und vom Druckerport brauchen wir den Universal Resource Name:

$deviceURN = (Get-PrinterPort -Name $printer.PortName).DeviceUUID

Damit bekommt man dann sowas zurück: URN:UUID:6D4FF0CE-6B11-11D8-8020-001E8F1CF942. Mit dieser Information kann man noch nicht viel anfangen aber es ist eine eindeutige ID, welche nur dem spezifischen Drucker, weltweit einmalig zugeordnet ist. Mit dieser ID kann man nun in der Registrierung unter HKEY_LOCAL_MACHINE:\SYSTEM\CurrentControlSet\Enum\SWD\DAFWSDProvider\ fündig werden:

$location =  (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\DAFWSDProvider\$deviceURN" –Name LocationInformation).LocationInformation

In $location steht nun bereits die IP-Adresse und sieht z. B. so aus: http://192.168.0.138:80/wsd/mex. Fast am Ziel, man kann nun die IP-Adresse ganz einfach extrahieren:

([System.UriBuilder]::new($location)).host

Nun hat man endlich die IP-Adresse des Druckers. Um das alles etwas zu vereinfachen hier eine kleine Funktion:

Function Get-IPAddressFromWSDDevice ($Printer) {

$deviceURN = (Get-PrinterPort -Name $printer.PortName).DeviceUUID
$location =  (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Enum\SWD\DAFWSDProvider\$deviceURN" –Name LocationInformation).LocationInformation
([System.UriBuilder]::new($location)).host

}

Noch ein Hinweis. Da leider die passenden Cmdlets erst ab Windows 8 aufwärts verfügbar sind, wird diese Methode nicht auf Windows 7 funktionieren. Die hier gezeigte Variante funktioniert übrigens auch bei IPv6-Adressen.

Probleme mit Installation von Microsoft Azure Powershell

21 Oktober 2016

Nachdem nach der Ignite Version 3.0.0 der Microsoft Azure Powershell Module erschienen ist https://azure.microsoft.com/en-us/blog/azure-powershell-300/, dachte ich ich versuche nochmal zwei Rechner auf diese Version zu aktualisieren, die davor sich verweigert haben. Doch leider wie seither ohne Erfolg. Ich hätte hier jetzt die Meldungen aufgezeigt, doch dank Windows 10 unermüdlichen Neustarts sind diese verloren gegangen. Deshalb hier nur der Verweis auf die Lösung.

Zunächst wurde versucht die betreffenden Module wie hier beschrieben zu installieren: https://github.com/PowerShell/PowerShell/issues/1874#issuecomment-241170545. Klappte jedoch leider nicht. Danach weitergesucht und am Ende sagte hier jemand was von Verzeichnissen löschen: https://powershell.org/forums/topic/unable-to-install-module-azurerm/.

Tatsächlich, nachdem in den Verzeichnissen

%ProgramFiles%\WindowsPowerShell\Modules folder
%ProgramFiles(x86)%\Microsoft SDKs\Azure\PowerShell

alles gelöscht wurde, was nach Azure roch, funktionierte danach endlich das Install-Module AzureRM.

Source auf Github: https://github.com/Azure/azure-powershell

Direkte Kommunikation mit Druckern unter Windows von Powershell aus

17 Oktober 2016

Manchmal ist es hilfreich, wenn man unter Windows direkt etwas ausdrucken kann. Direkt in diesem Fall bedeutet aber nicht über die üblichen Wege einen Druckertreiber anzusprechen, sondern, dass man direkt Steuerzeichen an einen Drucker senden kann. Man hat z. B. einen Druck in eine Datei umgeleitet und möchte nun diese Datei später an den Drucker senden. Dies ist mit den üblichen unter Powershell zur Verfügung stehenden Cmdlets wie Out-Printer nicht möglich.

Aber wie immer kann man das .Net Framework zu Hilfe nehmen. Grundlage für diesen Artikel ist der Knowledge Base Artikel https://support.microsoft.com/en-us/kb/322091. Dieser beschreibt die direkte Kommunikation mit Druckern unter C#. Hier hat sich nun jemand bereits die Mühe gemacht die Grundlage des Artikels an Powershell anzupassen: http://panchosoft.blogspot.de/2013/07/imprimir-en-impresora-de-etiquetas-sato.html. Das interessante an dieser Methode ist, dass es keine Rolle spielt, ob der Drucker per LPT- oder USB-Schnittstelle mit dem Rechner verbunden ist, noch ob mittels Netzwerk über TCP/IP oder WSD-Diensten mit ihm kommuniziert wird.

Die Daten finden mittels dieser Methode immer in Reinform ihren Weg zum Drucker, unabhängig vom Anschluss bzw. Übertragungsweg. Die generelle Druckvorgehensweise unter Windows ist hier beschrieben: https://msdn.microsoft.com/en-us/library/windows/desktop/dd145115%28v=vs.110%29.aspx.

Ein Aspekt ist noch wichtig. Es gibt sogenannte Typ 3 und Typ 4 Druckertreiber, dazu ein anderes Mal mehr. Typ 4 kommen verstärkt seit Windows 8 und nachfolgend zum Einsatz. Damit der Ansatz mit dem direkten senden von Daten an den Drucker klappt, muss dies berücksichtigt werden: https://msdn.microsoft.com/en-us/library/windows/desktop/ff686812(v=vs.110).aspx.

Hier zunächst das gesamte Script:

# Powershell RAW Print

$src = @‘
using System;
using System.Runtime.InteropServices;

public class RawPrinterHelper
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
  [MarshalAs(UnmanagedType.LPStr)]
  public string pDocName;
  [MarshalAs(UnmanagedType.LPStr)]
  public string pOutputFile;
  [MarshalAs(UnmanagedType.LPStr)]
  public string pDataType;
}

  [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

  [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool ClosePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

  [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool EndDocPrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool StartPagePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool EndPagePrinter(IntPtr hPrinter);

  [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
  public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

}

‚@

Add-Type -TypeDefinition $src -Language CSharpVersion3

function Out-RawPrinter {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True, ParameterSetName="Printer", Position=0)]
        [PSTypeName(‚Microsoft.Management.Infrastructure.CimInstance#ROOT/StandardCimv2/MSFT_Printer‘)]
        [ciminstance]
        $Printer,
        [Parameter(Mandatory=$True, ParameterSetName="PrinterName", Position=0)]
        [String]$PrinterName,

        [String]$Output,
        [Byte[]]$ByteArray,
        [String]$DocTitle = "Powershell RAW print",
        [String]$DataType
    )

    If ($PSCmdlet.ParameterSetName -eq "PrinterName") {
        $Printer = Get-Printer $PrinterName
    }

    #
    If (((-Not ($Output)) -and (-Not ($ByteArray)))) {
        throw "Need some data, either by providing -Output or -ByteArray"
    }

    If ($Output) {
        $ByteArray = [System.Text.Encoding]::ASCII.GetBytes($Output)
        Write-Verbose "converted Output via ASCII-Encoding into ByteArray"
    }

    # DOCInfo Struktur erzeugen
    $di=New-Object RawPrinterHelper+DOCINFOA

    If (-Not ($DataType)) {
        # https://support.microsoft.com/en-us/kb/2779300 defines XPS_PASS
        If ((Get-PrinterDriver -Name $Printer.DriverName).MajorVersion -eq 4) {
            $di.pDataType = "XPS_PASS"
        } else {
            $di.pDataType = "RAW"
        }
    } else {
        $di.pDataType = $DataType
    }
    Write-Verbose "Type: $($di.pDataType)"
    $di.pDocName= $DocTitle

    # benötigte Variablen
    $hPrinter=[IntPtr]::Zero
    $dwWritten=0
    $result = $false

    If ([RawPrinterHelper]::OpenPrinter($Printer.Name.Normalize(), [ref] $hPrinter, [intptr]::Zero)) {
        If ([RawPrinterHelper]::StartDocPrinter($hPrinter, 1, $di)) {
            #$result = [RawPrinterHelper]::StartPagePrinter($hPrinter)
            $pa=[System.Runtime.InteropServices.GCHandle]::Alloc($ByteArray, [System.Runtime.InteropServices.GCHandleType]::Pinned)
            $result = [RawPrinterHelper]::WritePrinter($hPrinter, $pa.AddrOfPinnedObject(), $ByteArray.Length, [ref] $dwWritten);$Win32Error =  [ComponentModel.Win32Exception][System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
            # Win32Error, see http://www.exploit-monday.com/2016/01/properly-retrieving-win32-api-error.html
            Write-Verbose "$dwwritten Bytes sent to Printer ‚$($Printer.Name)‘, expected: $($ByteArray.Length)"
            $pa.Free()
            #$result = [RawPrinterHelper]::EndPagePrinter($hPrinter)
            $result = [RawPrinterHelper]::EndDocPrinter($hPrinter)
        }
        $result = [RawPrinterHelper]::ClosePrinter($hPrinter)
    }

}

Wie immer ist das Script nicht schön in dieser Darstellung aber es funktioniert per Copy&Paste.

Zunächst braucht man einen Druckernamen:

PS > Get-Printer| select Name

Name
—-
HP Officejet Pro X576 MFP PCL6
HP LaserJet A4/Letter PS Class Driver
HP Universal Printing PCL 6 (v6.3.0)
Microsoft XPS Document Writer
Microsoft Print to PDF
Fax
An OneNote 2013 senden

Nun wählt man am besten einen aus, in diesem Fall den ersten:

PS > $p=(get-printer)[0]
PS > $p

Name                           ComputerName    Type         DriverName
—-                           ————    —-         ———-
HP Officejet Pro X576 MFP PCL6                 Local        HP Officejet Pro X576

Nun kann man diesem Drucker Daten zukommen lassen:

$Schachttest ="$([char]27)&l4HSchacht1$([char]12)"+"$([char]27)&l1HSchacht2$([char]12)"

Out-RawPrinter –Printer $p –Output $Schachttest

Der etwas verkünstelte String druckt bei einem HP-Drucker zwei Seiten, eine vom Schacht1 (manueller) und eine vom Schacht2. Würde man in diesem Fall Out-Printer benutzen, dann würden die Steuerzeichen nicht interpretiert und als Text oder nichtdarstellbare Zeichen gedruckt.

Da die Umwandlung des Strings bei Out-RawPrinter mittels ASCII-Encoding stattfindet, stößt man auch schnell an eine Grenze, wenn man bestimmte Umlaute drucken möchte. Deshalb gibt es noch den Paramter –ByteArray anstatt –Output, somit kann man eine eigene Umwandlung durchführen und die Daten übergeben.

Dies kann man sich auch zunutze machen, wenn man einen Ausdruck in einer Datei umgelenkt hat und diese Datei dann später drucken möchte. Hier exemplarisch, wie man vorgeht:

$DruckDaten = Get-Content DruckDatei.PRN –Encoding Byte
Out-RawPrinter –Printer $p –ByteArray $DruckDaten

Neben diesen Möglichkeiten zur Kommunikation mit Druckern, möchte ich noch auf SNMP hinweisen, welches auch unter Windows 10 immer noch funktioniert, wie hier beschrieben: https://newyear2006.wordpress.com/2016/07/10/hp-netzwerkdrucker-per-remote-und-reboot-txt-neu-starten-nix-klappt-aber-snmp-bringt-die-lsung/.

Große Dateien mit Powershell splitten und wieder zusammenbauen

8 Oktober 2016

Aufgrund eines aktuellen Falles, wo große Dateien mittels Teamviewer übertragen werden mussten, die Verbindung aber mehr als instabil war, musste eine alternative Lösung her. Ein anderes Programm für die Datenübertragung zu installieren war aber nicht möglich.

Da bei jedem Abbruch des Teamviewers die Datenübertragung der großen Dateien nochmal von neuem gestartet werden musste, mussten die großen Dateien in möglichst kleine Einheiten umgepackt werden, damit diese, solange die Verbindung stabil war, erfolgreich übertragen werden konnten. Dabei gilt natürlich je kleiner die Pakete desto höher die Wahrscheinlichkeit der erfolgreichen Übertragung vor dem nächsten Abbruch.

Hier eine Funktion, welche den Part mit dem in kleinere Einheiten verpacken übernimmt:

Function Split-File{
    [CmdletBinding()]
    Param(
        [String]$File, 
        [String]$Prefix,
        [Int32] $ChunkSize
    )

    $inFile = Resolve-Path -Path $File -ErrorAction SilentlyContinue
    If (-Not $inFile) {
        throw "Unable to locate $File"
    }

    If (Test-Path $Prefix -IsValid) {
        If (Split-Path $Prefix) {
            $Prefix = Join-Path -Path (Resolve-Path (Split-Path $Prefix -Parent)) -ChildPath (Split-Path $Prefix -Leaf)
        }
    }
    Write-Verbose "InFile $inFile"
    Write-Verbose "OutPrefix $Prefix"

    # calculate amount of chunks
    $chunks = ([Math]::Round((dir $inFile).Length / $ChunkSize))
    $chunkLength = ([string]$chunks).length
    $chunkFormat = "{0,$($chunkLength):$(‚0‘ * $chunkLength )}"
    Write-Verbose "Chunks $chunks"
    Write-Verbose "Chunkformat $chunkFormat"

    $inStream = [System.IO.File]::OpenRead($inFile)
    $chunkNum = 1
    $bytes = [Byte[]] 0*$ChunkSize

    While ( $bytesRead = $inStream.Read($bytes,0,$ChunkSize)) {
       
        $outFile = "$Prefix$chunkFormat" -f $chunkNum
        $outStream = [System.IO.File]::OpenWrite($outFile)
        $outStream.Write($bytes,0,$bytesRead);
        $outStream.Close();
        Write-Verbose "ChunkFile $outFile"
        $chunkNum += 1
    }

    $inStream.Close()
}

Gegeben sei eine große Datei mit Namen GroßeDatei.ZIP mit 1,28GB, diese soll in kleine Einheiten a 100MB umgepackt werden.

Damit kann man z. B. diesen Aufruf durchführen:

Split-File -File .\GroßeDatei.ZIP -Prefix .\KleineDatei -ChunkSize 100MB –Verbose

als Ausgabe erscheint:

AUSFÜHRLICH: InFile C:\temp\GroßeDatei.ZIP
AUSFÜHRLICH: OutPrefix C:\temp\KleineDatei
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei01
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei02
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei03
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei04
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei05
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei06
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei07
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei08
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei09
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei10
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei11
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei12
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei13
AUSFÜHRLICH: ChunkFile C:\temp\KleineDatei14

Diese 100MB Pakete lassen sich nun wesentlich einfacher Übertragen und wenn doch eine Übertragung nicht klappt, muss nur das betreffende Paket nochmal übertragen werden.

Soweit so gut. Hat man nun alle Pakete auf der anderen Seite, dann muss man die Ursprungsdatei aber auch wieder zusammenbauen.

Dies geht ausnahmsweise ohne Powershell und direkt in der Eingabeaufforderung mittels COPY-Befehl:

Copy /B .\KleineDatei01 + .\KleineDatei02 TestDatei
Copy-Item : Es wurde kein Positionsparameter gefunden, der das Argument "+" akzeptiert.
In Zeile:1 Zeichen:1
+ copy /B .\KleineDatei01 + .\KleineDatei02 TestDatei
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Copy-Item], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.CopyItemCommand

Ups oder doch nicht? Die Erklärung ist ganz einfach, Powershell hat einen COPY-Alias (Get-Alias Copy) welcher auf Copy-Item verweist und Copy-Item ist eben nicht Syntaxkompatibel zum reinen Eingabeaufforderung COPY-Befehl. Aber man kann die zugehörige Shell explizit starten:

cmd.exe /C copy /B .\KleineDatei01 + .\KleineDatei02 + …  GroßeDatei.ZIP
.\KleineDatei01
.\KleineDatei02

        1 Datei(en) kopiert.

Damit ist wieder alles so wie es ursprünglich auf der anderen Seite war. Es gibt noch alternative Methoden die Dateien zusammenzubauen aber da spielt auch die Performancefrage eine Rolle: http://stackoverflow.com/questions/1783554/fast-and-simple-binary-concatenate-files-in-powershell.

Wenn man aber bei der einfachen Shell-Copy-Methode bleibt, dann kann es ziemlich mühsam werden, wenn man mehr als 5 Dateien zusammenbauen muss. Deshalb hier noch eine kleine Hilfe mittels Powershell:

(dir kleinedate*).name -join ‚ + .\‘

# Unter Powershell 2.0 verwendet man
(dir kleinedate* | select -ExpandProperty Name) -Join ‚+ .\‘

erzeugt:

KleineDatei01 + .\KleineDatei02 + .\KleineDatei03 + .\KleineDatei04 + .\KleineDatei05 + .\KleineDatei06 + .\KleineDatei07 + .\KleineDatei08 + .\KleineDatei09 + .\KleineDatei10 + .\KleineDatei11 + .\KleineDatei12 + .\KleineDatei13 + .\KleineDatei14

Damit kann man dann ganz einfach

cmd.exe /C COPY /B KleineDatei01 + .\KleineDatei02 + .\KleineDatei03 + .\KleineDatei04 + .\KleineDatei05 + .\KleineDatei06 + .\KleineDatei07 + .\KleineDatei08 + .\KleineDatei09 + .\KleineDatei10 + .\KleineDatei11 + .\KleineDatei12 + .\KleineDatei13 + .\KleineDatei14 GroßeDatei.ZIP

Damit ist die Sache fast perfekt. Man kann noch eine Sicherung einbauen, um zu bestätigen, dass die Daten korrekt übertragen wurden, indem man einen Hash Wert der Daten berechnet. Damit wird gewährleistet, dass das Zusammenbauen der Blöcke auch in der korrekten Reihenfolge erfolgte.

Dazu verwendet man Get-FileHash, falls man Powershell 2.0 einsetzt, findet man hier eine Lösung: https://newyear2006.wordpress.com/2011/07/25/md5-und-sha1-datei-checksummen-mittels-powershell-errechnen-ein-ersatz-fr-fciv/.

Windows 10 Device Guard

2 Oktober 2016

Ein sinnvolles Feature um einen Windows Rechner sicherer zu machen aber leider nur bei Enterprise Versionen verfügbar. Damit kann man mittels Credential Guard seine Passwörter und Tickets absichern.

Aber da ich davon ausgehe, dass in künftigen Versionen noch mehr Features von Device Guard in alle Windowsversionen Einzug halten, hier ein paar Dinge, die wichtig sind.

Wie immer hier die Einleitung zum Thema: https://technet.microsoft.com/en-us/itpro/windows/keep-secure/credential-guard.

Wie kann man per Powershell feststellen, ob Device Guard verfügbar ist bzw. Credential Guard läuft? Am einfachsten mittels WMI:

PS> Get-CimInstance -Namespace root/Microsoft/Windows/DeviceGuard -ClassName Win32_DeviceGuard

AvailableSecurityProperties                  : {0}
CodeIntegrityPolicyEnforcementStatus         : 0
InstanceIdentifier                           : 4ff40742-2649-41b8-bdd1-e80fad1cce80
RequiredSecurityProperties                   : {0}
SecurityServicesConfigured                   : {0}
SecurityServicesRunning                      : {0}
UsermodeCodeIntegrityPolicyEnforcementStatus : 0
Version                                      : 1.0
VirtualizationBasedSecurityStatus            : 0
PSComputerName                               :

Obiges Beispiel ist von einem älteren Rechner (BIOS), welcher Windows 10 Pro v1607 (32-Bit) installiert hat und keine Virtualisierungsfeatures aktiviert hat.

Hier ein etwas neuerer Rechner (UEFI) ebenfalls v1607 Pro (64-Bit) aber mit lokalem aktiviertem Hypervisor:

PS> get-ciminstance -Namespace root/Microsoft/Windows/DeviceGuard -ClassName Win32_DeviceGuard

AvailableSecurityProperties                  : {1, 3}
CodeIntegrityPolicyEnforcementStatus         : 0
InstanceIdentifier                           : 4ff40742-2649-41b8-bdd1-e80fad1cce80
RequiredSecurityProperties                   : {0}
SecurityServicesConfigured                   : {0}
SecurityServicesRunning                      : {0}
UsermodeCodeIntegrityPolicyEnforcementStatus : 0
Version                                      : 1.0
VirtualizationBasedSecurityStatus            : 2
PSComputerName                               :

Für die Ausgangsfrage ist, wie weiß man, dass Device Guard mit Credential Guard eingerichtet ist? Dazu fragt man SecurityServicesConfigured ab, wenn dieser Wert 1 oder höher enthält, dann ist er aktiv. Eine Beschreibung zu SecurityServicesConfigured gibt es unter https://technet.microsoft.com/de-de/itpro/windows/keep-secure/deploy-device-guard-enable-virtualization-based-security.

Wenn man wissen möchte, ob der Dienst auch läuft schaut man bei SecurityServicesRunning nach:

$DevGuard = Get-CimInstance –ClassName Win32_DeviceGuard 
–Namespace root\Microsoft\Windows\DeviceGuard;

If ($DevGuard.SecurityServicesConfigured -contains 1)
{"Credential Guard configured"};
If ($DevGuard.SecurityServicesRunning -contains 1)
{"Credential Guard running"}
https://blogs.technet.microsoft.com/poshchap/2016/09/23/
security-focus-check-credential-guard-status-with-powershell/
Übrigens auf einem Windows 10 Pro v1511 gibt es den Namespace root/
Microsoft/Windows/DeviceGuard gar nicht erst:

PS> Get-CimClass -Namespace root/Microsoft/Windows/DeviceGuard

Get-CimClass : Ungültiger Namespace

In Zeile:1 Zeichen:1

+ Get-CimClass -Namespace root/Microsoft/Windows/DeviceGuard

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : MetadataError: (:) [Get-CimClass], CimException

    + FullyQualifiedErrorId : HRESULT 0x8004100e,Microsoft.Management.Infrastructure.CimCmdlets.GetCimClassCommand

Powershell-Skripte mit Dateien aus dem Windows Explorer mittels Drag&Drop versorgen

12 August 2016

Man wundert sich immer wieder, dass unter Windows die Drag&Drop-Methode seltener zum Einsatz kommt als z. B. bei Mac-Benutzern. Während für viele Mac-Benutzer dies zum täglichen Brot gehört, tun sich Windows Benutzer oft damit schwer, wohl auch aus der Erfahrung heraus, dass bestimmte logische Drag&Drop-Kombinationen einfach nicht funktionieren. Woran liegt dies? An den faulen Programmierern! Um dieser Sache einen Riegel vorzuschieben, hier eine Beschreibung wie man Powershell-Skripte Drag&Drop fähig bekommt.

Folgende Ausgangssituation. Man hat ein sinnvolles Powershell-Skript, welches auf bestimmte Dateien angewendet werden soll. Man könnte nun unter Powershell das Skript schreiben und dem Benutzer sagen, er soll die betreffenden Dateien als Arrayparameter übergeben. Der kuckt einen blöd an und sagt: Bahnhof.

Um dem unbedarften Benutzer die Möglichkeit zu bieten bestimmte Powershell-Skripte auf bestimmte Dateien anzuwenden sollte die Sache über den Windows-Explorer funktionieren. D. h. der Benutzer markiert die gewünschten Dateien und zieht – also per Drag&Drop eine oder mehrere auf das Powershell-Skript und lässt sie dort los. Das Skript bekommt die ausgewählten Dateien als Parameter übergeben und kann seiner Arbeit nachgehen.

Von Hause aus ist dies leider in Powershell nicht vorgesehen. Aber mit einem kleinen Trick und Umweg über Batch-Dateien klappt es dann doch. Dazu benennt man am einfachsten die Batch-Datei wie die Datei mit dem Skript, nur anstatt der Endung .PS1 hat man eben .BAT  oder .CMD.

@ECHO OFF
REM Powershellscript.bat
REM
SET PSScript=%~dpn0.ps1
SET PSScriptPath=%~dp0
SET args=’%1′
:More
SHIFT
IF ‚%1‘ == “ GOTO Done
SET args=%args%,’%1′
GOTO More
:Done
REM funktioniert nur, wenn .BAT gleichen Dateinamen wie .PS1-Datei hat:
Powershell.exe -noprofile -noexit -command ". {$count=0; foreach($file in %args%) {. %PSScript% $file; $count++ }; ‚Processed ‚ + $count; }"

Hat man also das Skript MeinPowershellskript.PS1 und möchte dieses mittels Drag&Drop mit Dateien aus dem Windows Explorer befeuern, dann muss auch obige Batchdatei mit dem Namen MeinPowershellskript.BAT im selben Verzeichnis existieren. Der Benutzer zieht die Dateien auf die .BAT-Datei und nicht auf die .PS1-Datei!

Das Powershell-Skript kann dann mittels der immer verfügbaren Variablen $args die übergebene Datei abfragen und darauf reagieren, z. B. so:

"Datei zum Bearbeiten: $args[0]"
Read-Host "Return zum Beenden drücken"

Werden mehrere Parameter übergeben:

$Index=0
foreach($arg in $args) {
  "$(($Index++)): $arg"
}
Read-Host "Return zum Beenden drücken"

Im obigen Beispiel wird das Powershell-Skript für jede zu bearbeitende Datei separat hintereinander aufgerufen. Manchmal kann es dabei sinnvoll sein, den Pfad zu kennen, wo das Skript liegt. Um dies dem Powershell-Skript mitteilen zu können, kann man den Aufruf des Skripts etwas ändern, aus

{. %PSScript% $file; $count++ }

in

{. %PSScript% $file %PSScriptPath%; $count++ }

Warum aber die Skripte einzeln ausführen und nicht alle Dateien auf einen Schlag übergeben? Bei zu vielen Dateien geht die Eingabeaufforderung in die Knie und bringt blöder Fehlermeldungen, deshalb dieser Weg. Etwas langsamer und umständlicher aber dafür zuverlässiger.

Weitere Infos:

http://stackoverflow.com/questions/2819908/drag-and-drop-to-a-powershell-script

Invocation-Context:
http://gallery.technet.microsoft.com/scriptcenter/cb72e4e6-4a68-4a2e-89b7-cc43a860349e#content

Binärtyp einer EXE-Datei unter Windows ermitteln, ob 32- oder 64-Bit–man spricht auch von Bitness

27 Juli 2016

Möchte man unter Windows bei einer ausführbaren Datei wissen, ob diese eine 32-Bit oder 64-Bit Version ist, dann kennt die Win32-API die Funktion GetBinaryType. Mittels Powershell kann man diese Funktion wie folgt nutzen:

$MethodDefinition = @‘

// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364819(v=vs.85).aspx
[DllImport("kernel32.dll")]
public static extern bool GetBinaryType(string lpApplicationName, out uint lpBinaryType);

‚@

$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name ‚Kernel32‘ -Namespace ‚Win32‘ -PassThru

# Funktioniert aber nur, wenn man sich in einem 64-Bit Prozess befindet, also: [System.IntPtr]::Size -eq 8
$type=0
$Kernel32::GetBinaryType("c:\windows\system32\cmd.exe", [ref] $type)  # unter 64-Bit sollte es 6 sein
$type
$Kernel32::GetBinaryType("c:\windows\syswow64\cmd.exe", [ref] $type)  # unter 64-Bit sollte es 0 sein
$type

# mögliche Werte für $type:
# SCS_32BIT_BINARY = 0, // A 32-bit Windows-based application
# SCS_64BIT_BINARY = 6, // A 64-bit Windows-based application.
# SCS_DOS_BINARY = 1,   // An MS-DOS – based application
# SCS_OS216_BINARY = 5, // A 16-bit OS/2-based application
# SCS_PIF_BINARY = 3,   // A PIF file that executes an MS-DOS – based application
# SCS_POSIX_BINARY = 4, // A POSIX – based application
# SCS_WOW_BINARY = 2    // A 16-bit Windows-based application

Dabei ist zu beachten, wenn man den Test macht, dass IntPtr 8 liefern sollte, sonst befindet man sich in einem simulierten WOW64-32Bit-Prozess und bekommt verwunderliche Ergebnisse zurück.

Vorsicht mit Get-NetFirewallRule bzw. WMI MSFT_NetFirewallRule-Klasse bei Abfrage der Enabled-Eigenschaft – gilt allgemein bei $True und $False in Powershell

10 Juli 2016

Ein Script welches Windows Firewall Regel-Eigenschaften abfragte funktionierte nicht wie erwartet. Es wurde immer die Enabled Eigenschaft abgefragt aber es kam nicht das erwartete Ergebnis, wurde die Abfrage aber negiert, kam das erwartete Ergebnis heraus! Am Ende stellte es sich heraus, dass die Enabled-Eigenschaften kein klassisches Bool sondern einen String zurückgibt, hier die Story:

Fragen wir zunächst die aktuelle Anzahl von Regeln ab, hier bei einem aktuellen Windows 10:

$r=Get-NetFirewallRule
$r.length
413

Es gibt also 413 Regeln. Nun sollen alle Regeln ermitteln werden, die aktiviert sind:

($r | where enabled -eq $true).length
184

Jetzt sollen alle Regeln ermittelt werden, die nicht aktiviert sind:

($r | where enabled -eq $false).length
0

Die 0 ist jetzt nicht das erwartete Ergebnis, wenn man davon ausgeht, dass es 413 Regeln gibt, davon sind 184 aktiv, dann sollten mehr als 0 inaktiv sein.

Wo liegt das Problem? Das Problem ist, dass bei $True oder $False in Powershell immer True oder False am Bildschirm ausgegeben wird. Man interpretiert einen solchen Wert schnell als Boolean. Aber in diesem Fall hat man es nicht mit einem Boolean zu tun:

$r[0].enabled.pstypenames
Microsoft.PowerShell.Cmdletization.GeneratedTypes.NetSecurity.Enabled
System.Enum
System.ValueType
System.Object

Es ist also ein Aufzählungstyp!

($r | where enabled -eq "True").length
184
($r | where enabled -eq "False").length
229

So macht die Sache mehr Sinn!

Oder man macht die Abfrage über den passenden Typ:

($r| where enabled -eq ([Microsoft.PowerShell.Cmdletization.GeneratedTypes.NetSecurity.Enabled]:
:True)).length
184
($r| where enabled -eq ([Microsoft.PowerShell.Cmdletization.GeneratedTypes.NetSecurity.Enabled]:
:False)).length
229

Übrigens: $true –eq "True" ergibt immer True, auch wenn man $true –eq "irgendwas" setzt! Und $False –eq "False" ergibt immer False, egal mit was verglichen wird, außer $false!

Nützliche Powershellfunktionen zum Hin- und Herkonvertieren in Base64 und Splitten einer Zeichenkette

27 Mai 2016

Ohne viel blabla:

Function ConvertTo-Base64 {

    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [string]$String
    )
    [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($String))

}

Function ConvertFrom-Base64 {
 
    [CmdletBinding()]
    [OutputType([string])]
    Param (
        [string]$String
    )
    [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($String))

}

Function Split-String {

# splittet einen String in Blöcke mit einer bestimmten Länge

    [CmdletBinding()]
    Param (
        [parameter(Mandatory=$true)]
        [string]$String,
        [int]$BlockLength=60
    )
   
    $blocks = [System.Math]::Floor($String.Length / $BlockLength)
    $newString = ""

    for ($line = 0; $line -lt $blocks; $line++) {
        $newString += $String.Substring($line * $BlockLength, $BlockLength) + [System.Environment]::NewLine
    }

    If ($blocks * $BlockLength -lt $String.Length) {
        $newString += $String.Substring($blocks * $BlockLength)
    }

    $newString
}

Damit kann man eine Datei einlesen und in einen Base64-String umwandeln, etwas bearbeiten und wieder zurückwandeln:

$c=get-content -Raw C:\windows\win.ini
$b=ConvertTo-Base64 $c
# Split-String bricht den String nach 60 Zeichen um
$s=Split-String $b
$nc=ConvertFrom-Base64 (Split-String $s)
# Trotz Konvertierung und Splitten ist der konvertierte String identisch
$c -eq $nc