Archive for the ‘Powershell’ Category

ConsoleHost_history.txt von PSReadLine mit sinnvollen Metadaten erweitern

5 Mai 2024

PSReadline von Powershell speichert alle Kommandozeileneingaben von der Powershell in der Datei ConsoleHost_history.txt. Mittels

notepad (Get-PSReadLineOption).HistorySavePath

kann man ganz einfach in die gesammelten Eingaben Einblick erhalten. Dies ist manchmal sinnvoll um bestimmte Dinge nachvollziehen zu können.

Wenn man viel Zeit in der Powershell verbringt sammeln sich da viele Daten über die Zeit an. Da nie klar ist, wann eine Sitzung begann oder in welchem Kontext man sich bewegte, hier eine kleine Funktion die man aufrufen kann um den nötigen Kontext herstellen zu können:

Function HistoryDirDateTimeSyncPoint {Add-Content -path (Get-PSReadlineOption).HistorySavePath "`r`n# $env:COMPUTERNAME `"$pwd`" $(Get-Date)"}

Nun muss man nur

PS> HistoryDirDateTimeSyncPoint

aufrufen und bekommt diesen Eintrag

HistoryDirDateTimeSyncPoint

# COMPUTERNAME "C:\Users\Benutzer" 05/05/2024 18:13:30

Es wird also eine Leerzeile angefügt um optisch zu den vorhergehenden Eingaben einen Abstand zu bekommen und danach werden der Computername, der aktuelle Pfad und Datum mit Uhrzeit als Kommentarzeile eingetragen.

Obige Funktion ist in Windows Powershell 5 genauso wie in Powershell 7 verwendbar. Bei Powershell 7 funktioniert es auch unter MacOS und Linux.

Evtl. macht aber ein weiterer Parameter noch mehr Freude und zwar ob zum Zeitpunkt des Aufrufs Admin- oder Rootrechte aktiv waren. Dazu bauen wir uns diese Funktion:

Function Test-AdminOrRoot {

$root = $false

If ($PSVersionTable.Platform) {
  # Information verfügbar
  If ($PSVersionTable.Platform -eq ‚Win32NT‘) {
   # Windows
   $root = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
  } else {
   # Linux oder Mac
   $id = id -u
   If ($id -eq 0) {
    $root = $true
   } # else 501
  }
} else {
  # Information nicht verfügbar, kann nur Windows sein
  $root = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}
 
return $root
}

Diese bauen wir nun in die andere Funktion mit ein:

Function HistoryDirDateTimeSyncPoint {

        Function Test-AdminOrRoot {

        $root = $false

        If ($PSVersionTable.Platform) {
            # Information verfügbar
            If ($PSVersionTable.Platform -eq ‚Win32NT‘) {
                # Windows
                $root = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
            } else {
                 # Linux oder Mac
                $id = id -u
                 If ($id -eq 0) {
                    $root = $true
                 } # else 501
            }
        } else {
             # Information nicht verfügbar, kann nur Windows sein
             $root = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
        }
 
         return $root
    }

    Add-Content -path (Get-PSReadlineOption).HistorySavePath "`r`n# Admin/Root:$(Test-AdminOrRoot)  $env:COMPUTERNAME  `"$pwd`"  $(Get-Date)"

}

Im MacOS Terminal kann man dann das Ergebnis mittels

open (Get-PSReadLineOption).HistorySavePath

bewundern und stellt sich ungefähr so dar:

HistoryDirDateTimeSyncPoint

# Admin/Root:True    "/private/var/root"  05/05/2024 20:19:27

bzw. mit normalen Rechten:

HistoryDirDateTimeSyncPoint

# Admin/Root:False    "/Users/Benutzername"  05/05/2024 20:18:11

Windows “Abgesicherten Modus” loswerden

16 April 2023

Bei einer Teamviewersitzung war ein Neustart in den abgesicherten Modus notwendig. Leider gab es dabei Probleme und der Rechner bootete von nun an immer im abgesicherten Modus.

Wie man den abgesicherten Modus losbekommt wird hier umfassend dargestellt.

https://www.tenforums.com/tutorials/2304-boot-into-safe-mode-windows-10-a.html#option3

Wer das Problem schnell gelöst haben will nimmt die GUI-Anleitung her, wer es aber klassisch einfacher gelöst haben will nimmt natürlich die Eingabeaufforderung. Da aber heutzutage, vor allem durch die Terminal-App sich Powershell immer mehr in den Vordergrund rückt, kann es dabei zu einer etwas komischen Reaktion kommen.

Um den Abgesicherten Modus loszubekommen verwendet man meistens

bcdedit /deletevalue {current} safeboot

Allerdings wird dieser Aufruf in der Powershell mit dieser Meldung quitiert:

Der angegebene Löschbefehl ist nicht gültig.
Führen Sie "bcdedit /?" aus, um die Befehlszeilenunterstützung aufzurufen.
Falscher Parameter.

Nun kann man ewig daran rumbasteln, es wird nicht funktionieren, es sei denn man weiß, dass die geschweiften Klammern {} eine besondere Bedeutung als Skriptblöcke in Powershell haben: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_script_blocks?view=powershell-7.3.

Wenn man dies weiß, dann kann man das Problem einfach umschiffen, indem man z. B.

bcdedit /deletevalue "{current}" safeboot

verwendet. Problem gelöst.

Treiberversion von Windowsdruckertreibern ermitteln

20 Januar 2023

Lange Zeit gab es unter Windows nur die Druckertreiberversion 3. Als Windows Vista auf die Bühne kam brachte dies XPS-Druckertreiber-Unterstützung mit und damit Version 4.

Da Version 4 Druckertreiber selbst heute noch teilweise Probleme machen, z. B. beim Schachtwechsel bei bestimmten Druckjobs, ist man auf die Version 3 Druckertreiber angewiesen.

Wie kann man nun feststellen, ob man es mit einem V3 oder V4-Druckertreiber zu tun hat?

Eigentlich wäre die Sache recht simpel, man ruft z. B.

PS > Get-Printer -name ‚OneNote for Windows 10’|Get-PrinterDriver
Get-PrinterDriver : Es wurden keine MSFT_PrinterDriver-Objekte gefunden, bei denen die Name-Eigenschaft gleich
"OneNote for Windows 10" ist. Überprüfen Sie den Wert der Eigenschaft, und versuchen Sie es erneut.
In Zeile:1 Zeichen:44
+ Get-Printer -name ‚OneNote for Windows 10’|Get-PrinterDriver
+                                            ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (OneNote for Windows 10:String) [Get-PrinterDriver], CimJobException
    + FullyQualifiedErrorId : CmdletizationQuery_NotFound_Name,Get-PrinterDriver

Wie immer, könnte so einfach sein. Aber leider schafft es Powershell nicht die Verknüpfung zwischen Druckerwarteschlange und Druckertreiber aufzulösen.

In diesem Beispiel wird aber beschrieben, wie mittels WMI die fehlende Verbindung zwischen beiden hergestellt werden kann, nämlich mittels der Win32_DriverForDevice-Klasse.

https://devblogs.microsoft.com/scripting/how-can-i-retrieve-information-about-the-printer-driver-used-by-a-printer/

In Powershell gegossen, sieht das dann so aus:

PS> $printerName=’OneNote for Windows 10′
PS> $printer=gwmi win32_printer -Filter "Name=’$printerName’"
PS> $printerDriver=gwmi -Query "Associators Of {Win32_Printer.DeviceID=’$($printer.DeviceID)‘} WHERE AssocClass = Win32_DriverForDevice Role=Antecedent"
PS> $printerDriver.Version
4

Oder nun als einfache Funktion:

Function Get-PrinterDriverFromPrinter {
[CmdletBinding()]
Param(
  [String]$Name
)

$printer=gwmi win32_printer -Filter "Name=’$Name’" 
If ($printer) {
  $printerDriver=gwmi -Query "Associators Of {Win32_Printer.DeviceID=’$($printer.DeviceID)‘} WHERE AssocClass = Win32_DriverForDevice Role=Antecedent"
  If ($printerDriver) {
   $printerDriver
  }
}
}

Somit kann man diesen Aufruf zur Ermittlung der Druckertreiberversion verwenden:

PS> (Get-PrinterDriverFromPrinter -Name ‚OneNote for Windows 10‘).Version
4

Verzeichnis nach Dateiendung gruppieren in Powershell

7 Juni 2022

Ohne viel Erklärung als Gedankenstütze, welche Dateiendungen werden im Verzeichnis benutzt:

dir | group –Property {$_.Extension}

Oder nach Dateiendung sortieren:

dir | sort –Property {$_.Extension}

noch kürzer gehts natürlich so

dir | group {$_.Extension}

Soll das Ergebnis in einer bestimmten Reihenfolge ausgegeben werden, sollte man diesen Artikel beachten: https://stackoverflow.com/questions/48701384/powershell-custom-order-sorting.

4K Alignment der Partitionen unter Windows feststellen

21 Mai 2022

Will man in Windows auf die schnelle feststellen, ob die Partitionen auf einer Platte korrekt auf 4K ausgerichtet sind, gibt es leider keine direkte Möglichkeit dies in Erfahrung zu bringen.

Mit diesen drei Zeilen Powershell jedoch, bekommt man schnell alle relevanten Infos:

$partitions=Get-WmiObject –Class Win32_Diskpartition
$partitions | Add-Member –type ScriptProperty –Name Alignment4K –Value {$this.StartingOffset % 4096 –eq 0}
$partitions | select DeviceId, Alignment4K, Blocksize, @{N=’Size GB’;E={[math]::Round($_.Size/1GB,1)}}

Microsoft Translation API per Powershell nutzen um Bezeichnungen in anderen Sprachenversionen zu erhalten

9 April 2022

In diesem Artikel habe ich schon mal auf den Microsoft Online Übersetzungsdienst für UI-Begriffe verwiesen: https://newyear2006.wordpress.com/2016/08/27/offizielle-bezeichnungen-von-microsoft-produkten-in-anderen-sprachen-ermitteln/. Heute möchte ich darauf nochmal Bezug nehmen, da der Ursprungslink so nicht mehr funktioniert.

Der aktuelle Link für die Übersetzungen lautet: https://www.microsoft.com/en-us/language. Man gibt seinen Begriff, die Quell- und Zielsprache ein, wählt evtl. noch das Produkt aus und bekommt dann eine Liste mit Treffern der möglichen Übersetzungen ausgegeben.

Es gibt jedoch auch einen Web-Dienst welche die Übersetzungen machen kann, nähere Infos findet man in der Beschreibung hier: http://download.microsoft.com/download/1/5/D/15D3DDC6-7403-4366-BE99-AF5247ADEF1C/Microsoft-Terminology-API-SDK.pdf. Der Dienst verlangt aber SOAP-Requests, wodurch die Verwendung etwas komplizierter wird. Zum Glück gibt es aber bereits eine einfacher Powershell Funktion mit Namen Get-TerminologyTranslation, zu finden hier: https://github.com/beatcracker/Powershell-Misc, bzw. die Funktion direkt: https://raw.githubusercontent.com/beatcracker/Powershell-Misc/master/Get-TerminologyTranslation.ps1.

Die Anwendung ist denkbar einfach:

Get-TerminologyTranslation -Text ‚Info Center‘ -From ‚de-de‘ -To ‚en-us‘ -Source UiStrings

liefert dann ‘Action Center’ als Ergebnis zurück.

Wichtig: Get-TerminologyTranslation verwendet New-WebserviceProxy, welches bei Powershell 6 und 7 nicht mehr vorhanden ist! Man muss also Powershell 5.1 verwenden, siehe auch: https://github.com/PowerShell/PowerShell/issues/9838.

Microsoft Ereignisanzeige wird mit unnötigen DistributedCOM Meldungen mit Ereignis-ID 10016 geflutet

18 März 2022

Wer kennt sie nicht die unsäglichen Ereigniseintragungen von DistributedCOM mit der Ereignis-ID 10016.

Wir hatten schon mal den Hinweis auf die Erstellung von Ereignisanzeigen-Filtern hier: https://newyear2006.wordpress.com/2011/04/28/ereignisanzeige-query-builder-fr-powershell/, bzw. direkt mit der lästigen DCOM-Fehlermeldung auseinadergesetzt: https://newyear2006.wordpress.com/2010/08/21/lstige-dcom-fehlermeldungen-in-der-ereignisanzeige-mit-61738644-f196-11d0-9953-00c04fd919c1-und-sid-s-1-5-20/.

Selbst Microsoft hat mittlerweile ein Einsehen: https://docs.microsoft.com/de-de/troubleshoot/windows-client/application-management/event-10016-logged-when-accessing-dcom, schreibt aber

Diese Ereignisse können ignoriert werden, da sie sich nicht negativ auf die Funktionalität auswirken und entwurfsbedingt sind. Dies ist die empfohlene Aktion für diese Ereignisse.

Trotzdem blöd, wenn alle zwei Minuten mehrere solche Einträge generiert werden und die Übersicht über vielleicht wichtige Ereignisse zunichte macht.

Hier ein Script, welches Ereignisse eines bestimmten Zeitraums ausgibt, dabei aber die unnötigen DCOM-Eintragungen aber rausfiltert:

# bestimmter Zeitraum von vergangenen Tagen:
$fromDate=(Get-Date (get-date).AddDays(-10) -format ’s‘)
$toDate=(Get-Date (get-date).AddDays(-8) -format ’s‘)

# oder was ist in den letzten 60 Minuten geschehen:
$fromDate=(Get-Date (get-date).AddMinutes(-60) -format ’s‘)
$toDate=$null
If ($fromDate -and $toDate) {
  $dateFilter = "*[System[TimeCreated[@SystemTime>=’$fromDate‘ and @SystemTime<=’$toDate‘]]]"
} else {
  $dateFilter = "*[System[TimeCreated[@SystemTime>=’$fromDate‘]]]"
}
$xmlFilter=@"
<QueryList>
  <Query Id="0" Path="System">
    <Select Path="System">
$dateFilter
    </Select>
    <Suppress Path="System">
      *[System[(EventID=10016)]]
      and
      *[EventData[
        (
          Data[@Name=’param4′] and Data='{D63B10C5-BB46-4990-A94F-E40B9D520160}‘ and
          Data[@Name=’param5′] and Data='{9CA88EE3-ACB7-47C8-AFC4-AB702511C276}‘ and
          Data[@Name=’param8′] and Data=’S-1-5-18′
        )
        or
        ( Data[@Name=’param4′] and Data='{260EB9DE-5CBE-4BFF-A99A-3710AF55BF1E}‘ and
          Data[@Name=’param5′] and Data='{260EB9DE-5CBE-4BFF-A99A-3710AF55BF1E}‘
        )
        or
        (
          Data[@Name=’param4′] and Data='{C2F03A33-21F5-47FA-B4BB-156362A2F239}‘ and
          Data[@Name=’param5′] and Data='{316CDED5-E4AE-4B15-9113-7055D84DCC97}‘ and
          Data[@Name=’param8′] and Data=’S-1-5-19′
        )
        or
        (
          Data[@Name=’param4′] and Data='{6B3B8D23-FA8D-40B9-8DBD-B950333E2C52}‘ and
          Data[@Name=’param5′] and Data='{4839DDB7-58C2-48F5-8283-E1D1807D0D7D}‘ and
          Data[@Name=’param8′] and Data=’S-1-5-19′
        )
      ]]
    </Suppress>
  </Query>
</QueryList>
"@

Get-WinEvent -FilterXml $xmlFilter|Out-GridView

Barcodes mittels ZINT erstellen, Export mittels SVG sowie Erweiterung durch Powershell

3 April 2021

Mittel Zint lassen sich ganz einfach 1D und 2D Barcodes erstellen. Man unter Windows mittels einer GUI die Barcodes definieren aber auch mittels Kommandozeile. Die aktuelle Version findet man hier: https://sourceforge.net/projects/zint/files/zint/. Für folgende Zeilen verwenden wir die aktuell verfügbare Version 2.9.1 für Windows, https://sourceforge.net/projects/zint/files/zint/2.9.1/zint-2.9.1-win32.zip/download. Im Ordner zint-2.9.1 der ZIP-Datei findet man qtZint.exe das Programm für die GUI und zint.exe das Programm für die Kommandozeile.

Das tolle an Zint ist, dass alle möglichen Barcodes erzeugt und diese in viele unterschiedlichen Formaten exportiert werden können. Uns interessiert besonders der SVG Export. Denn mittels SVG kann man die erzeugten Barcodes optimal auf verschiedenen Geräten weiterverwenden.

Hier ein Beispiel wie ein einfacher DataMatrix Barcode erzeugt wird:

.\zint.exe -o test.svg –barcode=71 -d "Ein einfacher Test-Barcode im DataMatrix-Format"

Die exportierte SVG-Datei ist eine reine Textdatei im SVG-Format. Da mittlerweile alle Browser SVG-Dateien interpretieren können kann man mittels

start test.svg

den Barcode direkt am Bildschirm anschauen.

Ein Problem hierbei wird schnell offensichtlich, denn der Barcode wird oben links in der Ecke platziert und ist so mittels Barcodescanner nicht lesbar, denn es braucht die Ruhezone (Quiet Zone) um den DataMatrix-Barcode um diesen erkennen zu können.

Da SVG auf XML basiert können SVG-Dateien sehr einfach mittels Powershell weiterverarbeitet bzw. erweitert werden. Wir verwenden nun Powershell um die SVG-Datei zu laden und die bestehende Grafik zu erweitern.

$svg=[xml](Get-Content .\test.svg)
$defs=$svg.CreateElement("defs", $svg.DocumentElement.NamespaceURI)
$root=$svg.DocumentElement
$g=$svg.svg.g
$root.InsertBefore($defs, $g)|Out-Null
$defs.AppendChild($svg.svg.g)|Out-Null
$use=$svg.CreateElement("use", $svg.DocumentElement.NamespaceURI)
$att=$svg.CreateAttribute(‚href‘)
$att.Value=’#barcode‘
$use.Attributes.Append($att)|Out-Null
$root.InsertAfter($use, $defs)|Out-Null
$att=$svg.CreateAttribute(‚x‘)
$att.Value=’20‘
$use.Attributes.Append($att)|Out-Null
$att=$svg.CreateAttribute(‚y‘)
$att.Value=’20‘
$use.Attributes.Append($att)|Out-Null
$svg.svg[1].Attributes[‚width‘].Value=200
$svg.svg[1].Attributes[‚height‘].Value=200
$svg.Save($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(".\testneu.svg"))
start testneu.svg

Kurze Erklärung. Die SVG-Grafik des Barcodes findet sich zwischen den Elementen <g> und </g>. Diese wird in ein neu hinzugefügtes Element <defs> verschoben. Da nun der Barcode nur zu Verwendung deklariert wurde, wird das Element <use> hinzugefügt und dort der Barcode mit einer Positionsangabe ausgegeben. Damit der komplette Barcode ersichtlich ist, wird die Breite und Höhe des SVG Viewports noch erweitert.

Konsolen Cursor bei Windows per Powershell ein oder ausschalten

28 Oktober 2020

Da ein bestimmtes Script blöderweise immer den Cursor ausgeschaltet hat und am Ende dieser nicht mehr sichtbar ist, braucht es eine Lösung um den Cursor wieder einzuschalten. Anbei ein einfaches Script, welches über die Win32-Api den Cursor wieder einschalten kann.


$MethodDefinitions = @'
using System;
using System.Runtime.InteropServices;
public class ConsoleCursor {
[StructLayout(LayoutKind.Sequential)]
internal struct CONSOLE_CURSOR_INFO
{
internal uint Size;
internal bool Visible;
}
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool GetConsoleCursorInfo(IntPtr hConsoleOutput,
out CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetConsoleCursorInfo(IntPtr hConsoleOutput,
[In] ref CONSOLE_CURSOR_INFO lpConsoleCursorInfo);
internal static void SetCursor(bool OnOff)
{
var cci = new CONSOLE_CURSOR_INFO();
var handle = GetStdHandle(-11); // -11 = STD_OUTPUT_HANDLE
GetConsoleCursorInfo(handle, out cci);
cci.Visible = OnOff;
SetConsoleCursorInfo(handle, ref cci);
}
public static void On()
{
SetCursor(true);
}
public static void Off()
{
SetCursor(false);
}
}
'@
Add-Type $MethodDefinitions
[ConsoleCursor]::Off(); Start-Sleep -Seconds 2; [ConsoleCursor]::On()

Man kann also mittels [ConsoleCursor]::On() oder [ConsoleCursor]::Off() das verhalten steuern.

Powershell Prompt anpassen

29 September 2020

In Powershell lässt sich der Prompt, üblicherweise PS gefolgt vom Verzeichnisnamen, ganz einfach anpassen.

Man schreibt einfach eine Funktion mit dem Namen Prompt und gibt den gewünschten String zurück. Bevor man das aber macht, ist es interessant zu schauen, wie der aktuelle Prompt definiert ist:

get-content function:prompt

und man bekommt bei Windows Powershell 5.1 dies zurück:

"PS $($executionContext.SessionState.Path.CurrentLocation)$(‚>‘ * ($nestedPromptLevel + 1)) ";

Der NestedPromptLevel wird unter anderem beim Debuggen verwendet.

Wenn man auf die schnelle einen eigenen Prompt möchte, könnte man die einfachste Form so nachbauen:

function prompt {"PS $(Get-Location)> "}

Für viele Situationen reicht dies bereits, so kann man den Prompt um den Computernamen erweitern:

function prompt {"PS $env:COMPUTERNAME $(Get-Location)> "}

Die Azure Cloud Shell reagiert durch Abfrage von $pwd.Drive auf das aktuelle Laufwerk und verändert je nach Kontext dann die Ausgabe.

Die Funktionalität mit dem Prompt findet sich exakt auch bei Powershell Core und Powershell 7 wieder.

Verlässt man allerdings eine Shell, gehen auch die gemachten Änderungen verloren. Will man den Prompt dauerhaft speichern muss man dies in den Powershellprofilen tun.

Weitere Infos zum Prompt: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_prompts?view=powershell-7