Archive for the ‘Powershell’ Category

Feststellen welches Programm das Entfernen eines USB-Gerätes unter Windows verhindert

30 August 2017

Wenn man unter Windows USB Geräte abmeldet bzw. auswirft, kann es passieren, dass man mit dieser schönen Meldung beglückt wird:

—————————
Fehler beim Abdocken von "USB-Massenspeichergerät"
—————————
Das Gerät "USB-Massenspeichergerät" kann aufgrund eines unbekannten Fehlers nicht beendet werden. Entfernen Sie das Gerät nicht, solange es noch verwendet wird. Schließen Sie alle Programme, von denen das Gerät verwendet wird, und entfernen Sie es anschließend.
—————————
OK  
—————————

Unter Windows 7 sieht die Meldung etwas anders aus, ist aber genauso sinnlos:

—————————
Fehler beim Abdocken von "USB-Massenspeichergerät"
—————————
Dieses Gerät wird gerade verwendet. Schließen Sie alle Programme oder Fenster, die möglicherweise das Gerät verwenden, und wiederholen Sie den Vorgang.
—————————
OK  
—————————

Tja, “Unbekannte Fehler” sind des Benutzers liebste Fehler! Selbst heute in Zeiten von Windows 10 hat es Microsoft noch nicht geschafft diese Meldung mit mehr sinnvollen Informationen zu versehen und kommt immer noch so kryptisch wie zu Windows XP-Zeiten daher.

Aber dank Powershell kann der gequälte Anwender sich etwas mehr Informationen beschaffen. Zumindest bei Windows 7 bis Windows 10.

Get-WinEvent -ProviderName Microsoft-Windows-Kernel-PnP -MaxEvents 5 | where {$_.TimeCreated.Date -eq (Get-Date).Date -and $_.id -eq 225} | ft TimeCreated, Message –Wrap

Denn es werden in der Ereignisanzeige in Windows tiefergehende Informationen zum Vorgang gespeichert und damit kann man in der Regel den Grund für obige Fehlermeldung herausfinden. Man muss nur beim Microsoft-Windows-Kernel-PNP Provider nach der EventID 225 suchen.

In diesem Fall bekommt man z. B.:

TimeCreated         Message
———–         ——-
30.08.2017 17:33:47 Die Anwendung \Device\HarddiskVolume4\Windows\System32\cmd.exe mit der Prozess-ID                     21832 hat das Entfernen oder Auswerfen für das Gerät                     USB\VID_1E68&PID_0035\BB00000XXXXXXX beendet.

angezeigt. D. h. cmd.exe, also die Eingabeaufforderung, mit der ProzessID 21832 hatte Zugriff auf den USB-Stick und verhinderte dadurch das Auswerfen.

Advertisements

E-Mails mit SSL/TLS-Unterstützung mittels Powershell versenden

6 Juli 2017

Die Powershell Funktion Send-MailMessage https://msdn.microsoft.com/de-de/powershell/reference/5.1/microsoft.powershell.utility/send-mailmessage hat so ihre Tücken und hilft leider nicht, wenn es Probleme beim E-Mailversand gibt. Teilweise reagiert sie auch nicht wie erwartet. In der heutigen Zeit, vor allem wegen der TLS-Pflicht reagiert sie oft zickig in Bezug auf den UseSSL Parameter. Aus diesem Grund hier eine einfache Methode E-Mails mittels TLS per Powershell versenden zu können.

Wer Probleme beim E-Mailversand hat, der schaue sich https://newyear2006.wordpress.com/2015/08/23/probleme-mit-transportverschlsselung-zertifikaten-oder-passwrtern-bei-smtp-servern-mittels-powershell-berprfen/ an, dort wird im Detail beschrieben, wie dieses Skript funktioniert.

Gleichzeitig benutzt diese Routine nicht mehr die Test-NetConnection Funktion, da diese komischerweise nicht mehr den Socket öffnet. Statt dessen wird der benötigte TCP-Socket zu beginn manuell geöffnet. Der Rest sind nur Anpassungen für diese Änderung.

Man muss nur die im ersten Block aufgeführten Parameter eintragen und schon kann die Spammerei losgehen…

Hier der ganze Code:

$smtp = "smtp.web.de"
$smtpPort = 587
$user = "meinewebdeadresse@web.de"
# $userPass kann auch leer gesetzt werden, dann wird nachgefragt
$userPass = "meinPasswort"
$Subject = "Test Betreff"
$Body = "Test Body"
$from = "meinewebdeadresse@web.de"
$to = "meinewebdeadresse@web.de"
$xmailer = "Powershell v1"

$c=New-Object System.Net.Sockets.TcpClient
$c.Connect($smtp, $smtpPort)

# Antwort vom SMTP-Server holen und ausgeben
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# Begrüßung durchführen
$buffer=[System.Text.Encoding]::ASCII.GetBytes("EHLO $Env:Computername`r`n")
$c.Client.Send($buffer)
Sleep -Seconds 1

# Antwort vom SMTP-Server holen
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# STARTTLS-Anfrage starten
$buffer=[System.Text.Encoding]::ASCII.GetBytes("STARTTLS`r`n")
$c.Client.Send($buffer)
Sleep -Seconds 1

# Bei dieser Antwort sollte 220 rauskommen,
# sonst gibt es ein Problem
[byte[]]$buffer= @(0) * $c.Client.Available
$c.Client.Receive($buffer)
[System.Text.Encoding]::ASCII.GetString($buffer)

# für den weiteren Gang ist entscheidend, dass man einen Stream braucht
# vom Socket erhält man einen Stream, durch Weitergabe an NetworkStream-Klasse
$n=New-Object System.Net.Sockets.NetworkStream $c.Client

# über den Networkstream kann man nun SslStream aktivieren:
$ssl = New-Object System.Net.Security.SslStream $n, $true

# nun kann man den eigentlichen TLS Handshake starten, dazu muss nochmal der Host angegeben werden
$ssl.AuthenticateAsClient($smtp)

# für die weitere Kommunikation richtet man sich am besten zusätzliche Streams ein:
$reader = New-Object System.IO.StreamReader $ssl
$writer = New-Object System.IO.StreamWriter $ssl
$writer.AutoFlush = $true

# damit wird die weitere Kommunikation einfacher und das Spiel beginnt von vorne:
$writer.WriteLine("EHLO $Env:Computername")
while ($reader.Peek() -gt -1) {
    $reader.ReadLine()
}

$writer.WriteLine("AUTH LOGIN")
$reader.ReadLine()

[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("VXNlcm5hbWU6"))

If ($UserPass) {
  $Spass = ConvertTo-SecureString -String $UserPass -AsPlainText -Force
  $cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $SPass } else {
  $cred = Get-Credential –UserName $User –Message "Bitte Passwort eingeben"
}
$pass = $cred.GetNetworkCredential().Password
$userBytes = [System.Text.Encoding]::ASCII.GetBytes($user)
$userBase64 = [System.Convert]::ToBase64String($userBytes)
$writer.WriteLine($userBase64)
$reader.ReadLine()

$passBytes = [System.Text.Encoding]::ASCII.GetBytes($pass)
$passBase64 = [System.Convert]::ToBase64String($passBytes)
$writer.WriteLine($passBase64)
$reader.ReadLine()

$writer.WriteLine("MAIL FROM:$from")
$writer.WriteLine("RCPT TO:$to")
$writer.WriteLine("DATA")
#$writer.WriteLine("")
$writer.WriteLine("From: $from")
$writer.WriteLine("To: $to")
$writer.WriteLine("Date: {0}", (Get-Date).ToString("ddd, d M y H:m:s z"))
$writer.WriteLine("Subject: $Subject")
$writer.WriteLine("X-Mailer: $xmailer")
$writer.WriteLine("")
$writer.WriteLine("$Body")
$writer.WriteLine(".")
$writer.WriteLine("")
$writer.WriteLine("")
$reader.ReadLine()

# Streams und Sockets schließen
$ssl.Close()
$n.Close()
$c.Close()

Powershell Get-Credential aus Script heraus setzen

5 Juli 2017

In Powershell verwendet man häufiger Get-Credential um vom Benutzer Passwörter zu erhalten. Nun ist es beim Testen allerdings blöd immer ein Script zu haben, in dem man jedes mal etwas eintragen muss.

Anstatt nun Get-Credential zu verwenden, wäre es für den Workflow besser, man könnte Benutzernamen und Passwort direkt setzen. Dies geht, z. B. so:

$user = "Benutzer"
$pass = "Passwort"
$Spass = ConvertTo-SecureString –String $pass -AsPlainText -Force
$cred = New-Object -TypeName "System.Management.Automation.PSCredential" -ArgumentList $User, $SPass
$cred.GetNetworkCredential().Password

dies entspricht:

$cred = Get-Credential –Message "Bitte Password eingeben" –Username "Benutzer"

Am Ende ist $cred immer vom Typ PSCredential und kann im weiteren Script gleich verwendet werden, wie z. B.

$cred.GetNetworkCredential().Password

Weitere Infos zu SecureString hier: https://newyear2006.wordpress.com/2012/12/30/in-den-niederungen-von-securestring-mittels-powershell/ und hier: https://newyear2006.wordpress.com/2012/12/30/spa-mit-net-securestring-und-powershell-oder-sicheres-speichern-und-einlesen-von-passwrtern/

Neue Provisioning Cmdlets in Windows 10 v1703 Creators Update

4 April 2017

Beim durchschauen der neuen Windows 10 Version bin ich auf ein neues Powershell Modul gestoßen. Es nennt sich schlicht und ergreifend Provisioning ist aber bereits in Version 3 verfügbar.

Cool ein neues Modul und dann gleich Version 3! Bei der Recherche mittels Google war nichts in der Richtung zu finden, auch die Hilfe des Moduls zeigte nur ins Nirvana. Also eine echte Neuentdeckung Smiley.

So schaut man sich die Modul Infos an:

PS C:\WINDOWS\system32> get-module provisioning|fl *

LogPipelineExecutionDetails : False
Name                        : Provisioning
Path                        : C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Provisioning\provisioning.psm1
ImplementingAssembly        :
Definition                  : #
                              # Script Module file for provcmdlets module.
                              #
                              # Copyright (c) Microsoft Corporation
                              #
                              #
                              # Cmdlet aliases
                              #
                              Set-Alias Add-ProvisioningPackage Install-ProvisioningPackage
                              Set-Alias Remove-ProvisioningPackage Uninstall-ProvisioningPackage
                              Set-Alias Add-TrustedProvisioningCertificate Install-TrustedProvisioningCertificate
                              Set-Alias Remove-TrustedProvisioningCertificate Uninstall-TrustedProvisioningCertifi
                              Export-ModuleMember -Alias * -Function * -Cmdlet *
Description                 :
Guid                        : 1323f046-a4bd-47df-a8bc-8253eabc49b2
HelpInfoUri                 : http://go.microsoft.com/fwlink/?LinkId=525624
ModuleBase                  : C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Provisioning
PrivateData                 :
Tags                        : {}
ProjectUri                  :
IconUri                     :
LicenseUri                  :
ReleaseNotes                :
RepositorySourceLocation    :
Version                     : 3.0
ModuleType                  : Script
Author                      : Microsoft Corporation
AccessMode                  : ReadWrite
ClrVersion                  : 4.0
CompanyName                 : Microsoft Corporation
Copyright                   : © Microsoft Corporation. All rights reserved.
DotNetFrameworkVersion      :
ExportedFunctions           : {}
Prefix                      :
ExportedCmdlets             : {[Export-ProvisioningPackage, Export-ProvisioningPackage], [Export-Trace, Export-Tra
                              [Get-ProvisioningPackage, Get-ProvisioningPackage], [Get-TrustedProvisioningCertific
                              Get-TrustedProvisioningCertificate]…}
ExportedCommands            : {[Export-ProvisioningPackage, Export-ProvisioningPackage], [Export-Trace, Export-Tra
                              [Get-ProvisioningPackage, Get-ProvisioningPackage], [Get-TrustedProvisioningCertific
                              Get-TrustedProvisioningCertificate]…}
FileList                    : {}
CompatiblePSEditions        : {}
ModuleList                  : {}
NestedModules               : {provcmdlets}
PowerShellHostName          :
PowerShellHostVersion       :
PowerShellVersion           : 4.0
ProcessorArchitecture       : None
Scripts                     : {}
RequiredAssemblies          : {}
RequiredModules             : {}
RootModule                  : provisioning.psm1
ExportedVariables           : {}
ExportedAliases             : {[Add-ProvisioningPackage, Add-ProvisioningPackage],
                              [Add-TrustedProvisioningCertificate, Add-TrustedProvisioningCertificate],
                              [Remove-ProvisioningPackage, Remove-ProvisioningPackage],
                              [Remove-TrustedProvisioningCertificate, Remove-TrustedProvisioningCertificate]}
ExportedWorkflows           : {}
ExportedDscResources        : {}
SessionState                : System.Management.Automation.SessionState
OnRemove                    :
ExportedFormatFiles         : {}
ExportedTypeFiles           : {}

Bei den NestedModules fällt provcmdlets auf. Sucht man damit in Google findet man gerade mal 10 Einträge und alles erst seit Ende 2016. Also alles noch ganz heiß!

provcmdlets findet man im Verzeichnis C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Provisioning:

PS C:\WINDOWS\system32> dir C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Provisioning

    Verzeichnis: C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Provisioning

Mode                LastWriteTime         Length Name
—-                ————-         —— —-
-a—-       18.03.2017     21:57           6570 provautologger_add.reg
-a—-       18.03.2017     21:57            152 provautologger_del.reg
-a—-       18.03.2017     21:57          20992 provcmdlets.dll
-a—-       18.03.2017     21:57          90112 provcommon.dll
-a—-       18.03.2017     21:57           2238 provisioning.psd1
-a—-       18.03.2017     21:57            482 provisioning.psm1
-a—-       18.03.2017     21:57         745984 provpackageapi.dll
-a—-       18.03.2017     21:57           8378 provtrace.wprp
-a—-       18.03.2017     21:57          13312 wiminterop.dll

D. h. es gibt verschiedene DLL-Dateien und sogar Registry-Dateien die involviert sind. Bekommt man irgendwelche Infos von den DLL-Dateien?

PS C:\Windows\System32\WindowsPowerShell\v1.0\Modules\Provisioning> (dir).versioninfo| select comments, filedescription

Comments FileDescription
——– —————

 

 

         "ProvPackageAPI.DYNLINK"

Gähnende Leere. Schade.

Was ist nun an Cmdlets enthalten?

PS C:\WINDOWS\system32> get-command -Module
Provisioning

CommandType     Name                                               Version    Source
———–     —-                                               ——-    ——
Alias           Add-ProvisioningPackage                            3.0        Provisioning
Alias           Add-TrustedProvisioningCertificate                 3.0        Provisioning
Alias           Remove-ProvisioningPackage                         3.0        Provisioning
Alias           Remove-TrustedProvisioningCertificate              3.0        Provisioning
Cmdlet          Export-ProvisioningPackage                         3.0        Provisioning
Cmdlet          Export-Trace                                       3.0        Provisioning
Cmdlet          Get-ProvisioningPackage                            3.0        Provisioning
Cmdlet          Get-TrustedProvisioningCertificate                 3.0        Provisioning
Cmdlet          Install-ProvisioningPackage                        3.0        Provisioning
Cmdlet          Install-TrustedProvisioningCertificate             3.0        Provisioning
Cmdlet          Uninstall-ProvisioningPackage                      3.0        Provisioning
Cmdlet          Uninstall-TrustedProvisioningCertificate           3.0        Provisioning

Dabei verweisen die Add-Aliase auf die Install-Cmdlets und die Remove-Aliase auf die UnInstall-Cmdlets. Netto bleiben also folgende Cmdlets übrig:

Export-ProvisioningPackage
Export-Trace
Get-ProvisioningPackage
Get-TrustedProvisioningCertificate
Install-ProvisioningPackage
Install-TrustedProvisioningCertificate
Uninstall-ProvisioningPackage
Uninstall-TrustedProvisioningCertificate

Wenn man sich die Auflistung so anschaut, dann würde man meinen mit Get-ProvisioningPackage könnte man etwas anfangen. Dem ist leider nicht so. Auch Get-ProvisioningPackage –AllInstalledPackages brachte nichts zurück.

Also mal die Parameter so anschauen:

PS C:\> (get-command Get-ProvisioningPackage).Definition

Get-ProvisioningPackage [-PackageId] <string> [-LogsDirectoryPath <string>] [-WprpFile <string>]
[-ConnectedDevice] [<CommonParameters>]

Get-ProvisioningPackage [-PackagePath] <string> [-LogsDirectoryPath <string>] [-WprpFile
<string>] [-ConnectedDevice] [<CommonParameters>]

Get-ProvisioningPackage [-AllInstalledPackages] [-LogsDirectoryPath <string>] [-WprpFile
<string>] [-ConnectedDevice] [<CommonParameters>]

Jetzt hatte ich da so eine Idee, welche auch Version 3 erklärt. Denn es gibt in DISM sogenannte Provisioning Package Command Line Optionen https://msdn.microsoft.com/de-de/windows/hardware/commercialize/manufacture/desktop/dism-provisioning-package-command-line-options, welche seither noch keine Powershell Entsprechung gefunden haben. Wahrscheinlich sind die Provisioning Cmdlets darauf abgestellt?

Also die These mal schnell überprüft. Provisioning Packages haben ihre eigenen Dateiendungen nämlich .ppkg. Es finden sich auf Windows 10 Systemen im Verzeichnis C:\windows\Provisioning\Packages entsprechende Dateien:

PS C:\windows\Provisioning\Packages> dir

    Verzeichnis: C:\windows\Provisioning\Packages

Mode                LastWriteTime         Length Name
—-                ————-         —— —-
-a—-       18.03.2017     21:57           6139 Power.EnergyEstimationEngine.Control.ppkg
-a—-       18.03.2017     21:57           5654 Power.EnergyEstimationEngine.CPU.ppkg
-a—-       18.03.2017     21:57           5701 Power.EnergyEstimationEngine.Display.ppkg
-a—-       18.03.2017     21:57           5677 Power.EnergyEstimationEngine.MBB.ppkg
-a—-       18.03.2017     21:57           5622 Power.EnergyEstimationEngine.StandbyActivation.ppkg
-a—-       18.03.2017     21:57           6498 Power.EnergyEstimationEngine.Storage.ppkg
-a—-       18.03.2017     21:57           5637 Power.EnergyEstimationEngine.Telemetry.ppkg
-a—-       18.03.2017     21:57           5644 Power.EnergyEstimationEngine.Wifi.ppkg
-a—-       18.03.2017     21:57           6512 Power.Settings.Battery.ppkg
-a—-       18.03.2017     21:57           5718 Power.Settings.Button.ppkg
-a—-       18.03.2017     21:57           5612 Power.Settings.Disk.ppkg
-a—-       18.03.2017     21:57           5598 Power.Settings.Display.ppkg
-a—-       18.03.2017     21:57           5411 Power.Settings.EnergySaver.ppkg
-a—-       18.03.2017     21:57           5551 Power.Settings.IdleResiliency.ppkg
-a—-       18.03.2017     21:57           5631 Power.Settings.PCIExpress.ppkg
-a—-       18.03.2017     21:57          11478 Power.Settings.Processor.ppkg
-a—-       18.03.2017     21:57           6623 Power.Settings.Sleep.ppkg

Was passiert nun wenn man Get-ProvisioningPackage mit so einer Datei aufruft?

PS C:\windows\Provisioning\Packages> Get-ProvisioningPackage -PackagePath .\Power.Settings.Processor.ppkg

IsInstalled     : False
PackageID       : fc01e91f-914c-45af-9d7c-0b2e5fbedf62
PackageName     : Power.Settings.Processor
PackagePath     : C:\windows\Provisioning\Packages\Power.Settings.Processor.ppkg
Description     :
Rank            : 0
Altitude        : 0
Version         : 7.0
OwnerType       : Microsoft
Notes           :
LastInstallTime :
Result          :

Ja das sieht ja nun schon wesentlich besser aus. Also handelt es sich tatsächlich um Cmdlets um ppkg-Dateien verwalten zu können. These also bestätigt.

Und wo braucht man nun ppkg-Dateien konkret? Zur Einrichtung von Rechnern oder Handies, dazu musste man seither bei den Einstellungen auf Konten, dort auf Arbeits- und Schulkonto gehen und da dann auf Bereitstellungspaket hinzufügen oder entfernen. Dies geht nun wesentlich einfacher!

Hier eine Beschreibung zum Thema: https://technet.microsoft.com/en-us/itpro/windows/deploy/provisioning-how-it-works.

Man könnte nun noch viel mehr probieren aber die Richtung ist schon mal klar.

USB-Bootstick von einer Windows-ISO erstellen, MasterbootRecord (MBR)-Variante

6 März 2017

In Bezug zu diesem Artikel https://newyear2006.wordpress.com/2009/09/19/windows-vista-windows-7-usb-boot-stick-erstellen/, hier ein Update um die einzelnen Dinge per Powershell hinzubekommen. Dadurch wird es möglich eine beliebige aktuelle Microsoft ISO-Datei (von Vista bis Server 2016) zu verwenden und deren Inhalt auf einem bootbaren USB-Stick zu erhalten. Damit die unten stehenden Powershellbefehle verfügbar sind, braucht man mindestens Windows 8.1, getestet habe ich es mit Windows 10.

Diese Methode könnte man auch mit leichter Variation verwenden, um eine bootbare VHD oder VHDX für einen Hyper-V zu bekommen. Aber dies vielleicht ein anderes Mal.

Ein weiterer Vorteil bei diesem Weg kann man auch Modifikationen der zu installierenden Dateien vornehmen. So kann man z. B. die EI.CFG bearbeiten, bevor man die eigentliche Installation beginnt. https://newyear2006.wordpress.com/2009/12/13/windows-7-versionsauswahl-bei-installation/. Oder man könnte die Eingabe des Produktschlüssels umgehen: https://newyear2006.wordpress.com/2012/08/30/windows-8-eingabe-des-produktschlssels-bei-der-installation-umgehen/. Noch eine Variante: https://newyear2006.wordpress.com/2013/09/13/windows-8-1-mit-product-key-von-windows-8-installieren/.

Damit die folgenden Befehle auch funktionieren, braucht man eine Powershell-Eingabeaufforderung mit Administratorrechten.

Auswahl des USB-Sticks
Da wir hier gezielt die Daten auf einen USB-Stick schreiben wollen und bei den Aktionen auch mal schnell die falsche Platte gelöscht werden kann, lassen wir uns nur Geräte die als Wechseldatenträger (Removable Storage) auftauchen anzeigen.

$diskObj = Get-Volume | Where DriveType –eq Removable | Get-Partition | Get-Disk | Out-GridView –Title "USB-Stick auswählen" –PassThru

Um auf vom Volume zur Disk zu kommen, muss man den Umweg über die Partition machen. Am Ende sorgt Out-GridView, dass nur ein Stick ausgewählt werden kann.

Wer hier nichts angezeigt bekommt, der hat womöglich einen Stick der als interne Festplatte erkannt wird. Vor allem USB-Sticks die “Windows to Go” geeignet sind, werden so also nicht erkannt. Wer möchte, kann dazu diese Variante wählen:

$diskObj = Get-Disk | Out-GridView –Title "USB-Stick auswählen" –PassThru

Wer auf einem Hyper-V oder Nano-Server arbeitet, der hat Out-GridView nicht zur Verfügung, der kann auch diese Variante verwenden:

Get-Disk | select Number, FriendlyName, @{Name="Total Size"; Expression={"{0:N2} GB" –f ($_.Size/1GB)}}

Es werden alle Laufwerke aufgelistet und man wählt eines aus:

$diskObj = Get-Disk –Number X

Egal welche Methode man gewählt hat, man sollte nun in $diskObj ein Objekt vom Typ MSFT_Disk haben.

USB-Stick löschen und formatieren
Nun geht es ans eingemachte. Für alle, die nicht wissen, was sie tun, hier kann man schnell seine Platte löschen, wenn man oben nicht das richtige Laufwerk ausgewählt hat!

$diskNr = $diskObj.Number

Get-Disk –Number $diskNr | Clear-Disk –RemoveData

muss mit Ja bestätigt werden.

$part=New-Partition –DiskNumber $diskNr –UseMaximumSize –IsActive

$part | Format-Volume –FileSystem NTFS –NewFileSystemLabel "BootDisk"

hier darf kein –Full angegeben werden, dann ist es automatisch eine Quick-Formatierung.

Add-PartitionAccessPath –Disknumber $diskNr –PartitionNumber $part.PartitionNumber –AssignDriveletter

Laufwerksbuchstaben holen:

$drive = (Get-Partition –DiskNumber $diskNr –PartitionNumber $part.PartitionNumber).DriveLetter

ISO-Datei auswählen
So, nun muss das ISO-Image geladen werden, hier wieder komfortabel per Auswahl die ISO-Datei ausgewählt:

$isoFile = Get-Item .\myIsoPath\*.ISO | Out-GridView –Title "ISO auswählen" –PassThru

Möchte man eine ISO-Datei direkt angeben oder liegt die ISO-Datei im aktuellen Pfad, dann kann auch der direkte Weg benutzt werden:

$isoFile = Get-Item (Resolve-Path .\myIsoFile.ISO)

Nun muss die ISO-Datei eingehängt werden und der zugeordnete Laufwerksbuchstabe ermittelt werden. Allerdings gibt es hier mal wieder ein kleines Problem! Die ISO-Datei darf nicht mittels UNC-Pfad angegeben werden bzw. darf nicht auf einem Remotelaufwerk liegen, sonst wird sie nicht richtig eingehängt, bzw. es wird kein Laufwerksbuchstabe zugeordnet. Nachdem ich ewig lang gezweifelt habe, habe ich jemand anderes gefunden, der genau das gleiche Problem entdeckt hat: https://github.com/VirtualEngine/Lability/issues/28#issuecomment-159657191. Kurz gesagt, Get-Volume liefert nicht den Laufwerksbuchstaben der ISO-Datei, allerdings liefern PS-Drive, Win32_Volume den Laufwerksbuchstaben! Mal wieder ein krasser Bug. Also Vorsicht!

$image=Mount-DiskImage –ImagePath $isoFile.Fullname –PassThru
$imageDrive=($image|Get-Volume).DriveLetter

Bootsector schreiben
Damit von dem USB-Stick auch gebootet werden kann, muss natürlich ein Boot-Sektor geschrieben werden.

Start-Process –Wait "$($ImageDrive):\Boot\BootSect.Exe" –ArgumentList "/NT60","$($drive)"

Dateien der ISO auf den Stick kopieren:

xcopy.exe "$($ImageDrive):\*.*" "$($drive):\" /s /h

So nun sollte alles auf dem Stick sein, so dass von diesem gebootet werden kann.

Noch wichtig: Was tun, wenn der Stick beim Bootvorgang nicht auftaucht? Im UEFI BIOS sollte dann die SecureBoot-Einstellung überprüft werden, ob beliebige Betriebssysteme gebootet werden können oder nur SecureBoot-fähige.

Ein späterer Artikel soll die SecureBoot- also UEFI-fähige Variante nachliefern.

Verknüpfung auf Powershellbefehl mit Adminrechten

31 Januar 2017

Möchte man einem Benutzer erlauben z. B. die Druckerwarteschlange neu zu starten so benötigt dieser meist Adminrechte dafür. Man kann zwar die Rechte anders zuordnen aber bei Standardeinstellungen werden diese benötigt. Man kann zwar dem Benutzer eine einfache Verknüpfung zur Ausführung des Powershellbefehls einrichten aber das Anfordern der Adminrechte wird schon etwas komplizierter. Damit man nicht immer solange überlegen muss hier der passende Aufruf:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -Command "& {Start-Process powershell.exe -Verb runas -ArgumentList ‚-Command ""& {restart-service spooler -force -verbose; “Druckerdienst wurde neu gestartet.“; Start-Sleep -Seconds 5}"" ‚}"

Man kann die Zeile direkt bei der Angabe des Speicherorts einfügen und weiter klicken, den Namen der Verknüpfung noch nennen und schon ist man fertig. Der Befehl startet einfache eine zweite Powershellinstanz welche die Adminrechte anfordert und dann den Neustart der Druckwarteschlange ausführt.

Diese Konzept kann man natürlich noch weiter ausbauen um direkt jegliche Scripte mit Adminrechten ausführen zu können.

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/.