[PowerShell] Get-EventLog2 - (Sehr) Erweiterte Variante für die Eventlog-Ausgabe

DPXone

Lieutenant
Registriert
Mai 2009
Beiträge
554
Hi,

hier mal eine (bereits alte und öfters ausgebaute) PowerShell Funktion für die erweiterte Ausgabe des Eventviewer-Logfiles von mir.
Damit lassen sich alle Einträge in jedem Log-Provider abrufen.
Das erspart einiges an manueller Suche im Eventviewer, wenn man sich auf die Suche auf Microsoft-spezifische Fehler macht, die sich in allen möglichen "Unterordner" der Anwendungslogs von Microsoft befinden können.

Hier hab ich bereits ein Help-Text auf Englisch eingebaut. Multilingual geht leider nicht so einfach.

PowerShell-Events werden im Script standardmäßig unterdrückt, sonst sprengt das den Umfang, da das Script/die Funktion selbst PowerShell-Events auslöst

Beispiele finden sich unter EXAMPLE im Synopsis-Bereich oder, wie gewohnt, über Get-Help Get-EventLog2 -Full, wenn es als Modul (*.psm1) geladen wurde.

Get-EventLog2 | Ogv ohne Parameter gibt z. B. alle Events der letzten Stunde, die den Status: Warnung, Fehler oder Kritisch haben, in einem GridView aus.

PowerShell:
###############################################################################################################################################################################        
# Requirements:      
#     At least Windows PowerShell 3.0 is required to execute the function. Windows PowerShell is part of the Windows Management Framework (WMF).    
#     To obtain the latest version as of 12.2017, please install Windows Management Framework (WMF) 5.1 -> https://www.microsoft.com/en-us/download/details.aspx?id=54616      
#    
#    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!    
#    !!! WARNING: Do not install WMF 5.0 or 5.1 on server providing Microsoft Exchange Services!    
#    !!! Be also carefully with other Microsoft Server Services!    
#    !!!     
#    !!! Please check Product Compatibility of WMF 5.0 and 5.1 with other Microsoft Products:    
#    !!! WMF 5.0    -> https://docs.microsoft.com/en-us/powershell/wmf/5.0/productincompat    
#    !!! WMF 5.1    -> https://docs.microsoft.com/en-us/powershell/wmf/5.1/productincompat    
#    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!    
#    
# Check current version:      
#     To check which version is already installed on your Windows system,       
#     please enter the following built-in PowerShell environment variable in a PowerShell console (do not enter the '#', it's just a comment char):      
#      
#         $PSVersionTable      
#      
#     You should get some version numbers for protocols, assemblies, frameworks, ... used by PowerShell.      
#     Check the "PSVersion".      
#      
###############################################################################################################################################################################        
#Requires -Version 3.0    

Add-Type -TypeDefinition @'
   public enum EventLogLevel
   {
    L0_Undefined,
    L1_Critical,
    L2_Error,
    L3_Warning,
    L4_Information,
    L5_Verbose
   }
'@

Function Get-EventLog2 {
    <#  
    .SYNOPSIS
        Get events of all logs.

    .DESCRIPTION
        Get events of all logs on your local machine.

        Events of the 'PowerShell'-Eventlog-Providers are not listed by default!

        =================================================================
        Warning:
        =================================================================
        Be careful with the timespan between $StartTime and $EndTime because of performance issues!

    .PARAMETER EventLogLevel
        Enumerable values:
            L0_Undefined   = Indicates logs for an undefined message (=> is also shown as Level "Information" in Eventviewer but with log level 0).
            L1_Critical    = Indicates logs for a critical alert.
            L2_Error       = Indicates logs for an error.
            L3_Warning     = Indicates logs for a warning.
            L4_Information = Indicates logs for an informational message.
            L5_Verbose     = Indicates logs for a verbose message.

        Default = L1_Critical , L2_Error , L3_Warning
  
    .PARAMETER StartTime
        Date and time filter for the desired events.
        StartTime defines the start point for which the events should be gathered.

        Default = ( ( Get-Date ) - ( New-TimeSpan -Hours 1 ) )
  
    .PARAMETER EndTime
        Date and time filter for the desired events.
        EndTime defines the end point for which the events should be gathered.

        Default = ( Get-Date )

    .PARAMETER GroupMessages
        Group identical messages for a better overview with an additional counter column.

    .PARAMETER OutClipBoardBySystemListSeparator
        Output the result directly to the clipboard in a separated format.
        The list separator character used depends on the current system settings (see Control Panel -> Region -> Additional Settings)

        Can be used to paste the results directly in a Microsoft Excel Worksheet for example.

    .PARAMETER IncludePowerShellLogs
        Include the PowerShell log events.
        By default, all PowerShell events are excluded as many of them are raised by the script execution itself.

    .PARAMETER ProviderNameFilter
        Filter by Eventlog Provider Names.
        Wildcards * (matches zero or more characters) and ? (matches exactly one character) are allowed.

        ProviderNameFilter Examples:
            "Microsoft-Windows-Winlogon"
            "Microsoft-Windows*"
            "Microsoft-Windows-*logon"
            "Microsoft-???????-????????"

        Any * or ? in the filter which should not be used as wildcard has to be escapded by using a backslash!
        e. g.
        *    ->    \*
        ?    ->    \?

    .PARAMETER MessageFilter
        Filter by Message content.
        Wildcards * (matches zero or more characters) and ? (matches exactly one character) are allowed.

        MessageFilter Examples:
            "*Application error*"
            "Name resolution for the name*"
            "*did not register with DCOM within the required timeout."

        Any * or ? in the filter which should not be used as wildcard has to be escapded by using a backslash!
        *    ->    \*
        ?    ->    \?

    .PARAMETER EventIDFilter
        Filter by Event ID.
        Wildcards * (matches zero or more characters) and ? (matches exactly one character) are allowed.

        EventIDFilter Examples:
            "100"
            "17*"
            "*45"

        Any * or ? in the filter which should not be used as wildcard has to be escapded by using a backslash!
        *    ->    \*
        ?    ->    \?

    .EXAMPLE
        PS C:\> Get-EventLog2 | Format-Table -AutoSize -Wrap | Out-String -Width 4096


        List all 'critical', 'error' and 'warning' events (=default) which occurred in the last hour (=default)
        and show the results as string in an autosized and wrapped table.

    .EXAMPLE
        PS C:\> Get-EventLog2 -Levels (L1_Critical , L2_Error , L3_Warning) -StartTime (( Get-Date ) - ( New-TimeSpan -Hours 1 )) -EndTime (Get-Date) -GroupMessages | Out-GridView


        List all 'critical', 'error' and 'warning' events which occurred in the last hour,
        group it by messages and show the results in a gridview.

    .EXAMPLE
        PS C:\> Get-EventLog2 -OutClipBoardBySystemListSeparator


        List all 'critical', 'error' and 'warning' (=default) events which occurred in the last hour (=default)
        and output the result directly to the clipboard for pasting it to other application e. g. Notepad or Excel.

    .EXAMPLE
        PS C:\> Get-EventLog2 -MessageFilter 'Name resolution for the name*'


        List all 'critical', 'error' and 'warning' (=default) events which occurred in the last hour (=default)
        filtered by a regular expression created out of the filter 'Name resolution for the name*' (includes all Event Messages starting with the mentioned string followed by any characters (unlimited)).
      
    .EXAMPLE
        PS C:\> Get-EventLog2 -EventIDFilter '10?'


        List all 'critical', 'error' and 'warning' (=default) events which occurred in the last hour (=default),
        filtered by a regular expression created out of the filter '10?' (includes all Event IDs starting with 10 and with exactly one additional character).
      
    .EXAMPLE
        PS C:\> Get-EventLog2 -ProviderNameFilter 'Microsoft-Windows-GroupPolicy'


        List all 'critical', 'error' and 'warning' (=default) events which occurred in the last hour (=default)
        filtered by a regular expression created out of the filter 'Microsoft-Windows-GroupPolicy' (includes all Events of the mentioned Event Provider).
      
    .NOTES
        Created by DPXone on 2016-04-15
        Last modified by DPXone on 2017-12-21
        Published on: https://www.computerbase.de/forum/threads/powershell-get-eventlog2-sehr-erweiterte-variante-fuer-die-eventlog-ausgabe.1734277/  
    #>
  
    [CmdletBinding()]
    Param (
        [Alias('L')]
        [EventLogLevel[]] $EventLogLevel = @([EventLogLevel]::L1_Critical ,[EventLogLevel]::L2_Error ,[EventLogLevel]::L3_Warning) ,
      
        [Alias('ST')]
        [datetime] $StartTime = (Get-Date) - (New-TimeSpan -Hours 1) ,
      
        [Alias('ET')]
        [datetime] $EndTime = (Get-Date) ,
      
        [Alias('G')]
        [switch] $GroupMessages = $false ,
      
        [Alias('CB')]
        [switch] $OutClipBoardBySystemListSeparator = $false ,
      
        [Alias('IP')]
        [switch] $IncludePowerShellLogs = $false ,
      
        [Alias('PF')]
        [ValidateNotNullOrEmpty()]
        [string[]] $ProviderNameFilter = '*' ,
      
        [Alias('MF')]
        [ValidateNotNullOrEmpty()]
        [string[]] $MessageFilter = '*' ,
      
        [Alias('IDF')]
        [ValidateNotNullOrEmpty()]
        [string[]] $EventIDFilter = '*'
    )
    Begin {
        $Events = @()
        $objCol = @()
        $ListSeparator = (Get-Culture).TextInfo.ListSeparator
      
        $EventLogLevelInt = $EventLogLevel | % {[int] $_ }
        Function Create-WildcardAdjustedRegexTerm([string[]] $InputString) {
            $Result = @()
          
            $WildcardCharacters = @{
                EscapedAsteriskWildcard = @{ Original = '*' ; Escaped = '\*' ; Regex = '.*' ; TemporaryEscapeCharacter = '#%EscapedAstersik%#' }
                EscapedMatchOneWildcard = @{ Original = '?' ; Escaped = '\?' ; Regex = '.' ; TemporaryEscapeCharacter = '#%EscapedMatchOne%#' }
            }
          
            $Exclusions = @{
                DotExclusion = @{ Original = '.' ; Escaped = '\.' }
                DollarExclusion = @{ Original = '$' ; Escaped = '\$' }
                CircumflexExclusion = @{ Original = '^' ; Escaped = '\^' }
                PlusExclusion = @{ Original = '+' ; Escaped = '\+' }
            }
          
            Foreach ($InputStr In $InputString) {
                $FilterString = $InputStr
              
                # Exclude other characters (e.g. dot)       
                $FilterString = $FilterString.replace($Exclusions.DotExclusion.Original , $Exclusions.DotExclusion.Escaped)
                $FilterString = $FilterString.replace($Exclusions.DollarExclusion.Original , $Exclusions.DollarExclusion.Escaped)
                $FilterString = $FilterString.replace($Exclusions.CircumflexExclusion.Original , $Exclusions.CircumflexExclusion.Escaped)
                $FilterString = $FilterString.replace($Exclusions.PlusExclusion.Original , $Exclusions.PlusExclusion.Escaped)
              
                # Change wildcard characters       
                $FilterString = $FilterString.replace($WildcardCharacters.EscapedAsteriskWildcard.Escaped , $WildcardCharacters.EscapedAsteriskWildcard.TemporaryEscapeCharacter)
                $FilterString = $FilterString.replace($WildcardCharacters.EscapedMatchOneWildcard.Escaped , $WildcardCharacters.EscapedMatchOneWildcard.TemporaryEscapeCharacter)
              
                $FilterString = $FilterString.split($WildcardCharacters.EscapedAsteriskWildcard.Original)
                $FilterString = "(" + ($FilterString -join ")$($WildcardCharacters.EscapedAsteriskWildcard.Regex)(") + ")"
              
                $FilterString = $FilterString.Split($WildcardCharacters.EscapedMatchOneWildcard.Original)
                $FilterString = "(" + ($FilterString -join ")$($WildcardCharacters.EscapedMatchOneWildcard.Regex)(") + ")"
              
                $FilterString = $FilterString.replace($WildcardCharacters.EscapedAsteriskWildcard.TemporaryEscapeCharacter , $WildcardCharacters.EscapedAsteriskWildcard.Escaped)
                $FilterString = $FilterString.replace($WildcardCharacters.EscapedMatchOneWildcard.TemporaryEscapeCharacter , $WildcardCharacters.EscapedMatchOneWildcard.Escaped)
              
                $Result+= "($FilterString)"
            }
            Return $Result -join "|"
        }
      
        $ProviderNameRegexFilter = Create-WildcardAdjustedRegexTerm $ProviderNameFilter
        $EventIDRegexFilter = Create-WildcardAdjustedRegexTerm $EventIDFilter
        $MessageRegexFilter = Create-WildcardAdjustedRegexTerm $MessageFilter
      
        write-Verbose("{0,-25}: {1}" -f 'StartTime filter' , $StartTime)
        write-Verbose("{0,-25}: {1}" -f 'EndTime filter' , $EndTime)
        write-Verbose("{0,-25}: {1}" -f 'Levels' ,($EventLogLevel -join ', '))
        write-Verbose("{0,-25}: {1}" -f 'GroupMessages' , $GroupMessages)
        write-Verbose("{0,-25}: {1}" -f 'IncludePowerShellLogs' , $IncludePowerShellLogs -join ', ')
        write-Verbose("{0,-25}: {1}" -f "ProviderNameRegexFilter" , $ProviderNameRegexFilter)
        write-Verbose("{0,-25}: {1}" -f "EventIDRegexFilter" , $EventIDRegexFilter)
        write-Verbose("{0,-25}: {1}" -f "MessageRegexFilter" , $MessageRegexFilter)
      
    }
  
    Process {
        $Logs = Get-WinEvent -ListLog * -ErrorAction Ignore
      
        Foreach ($Log In $Logs) {
            $hash = @{ LogName = $Log.LogName ; Level = $EventLogLevelInt ; StartTime = $StartTime ; EndTime = $EndTime }
            $tempEvents = Get-WinEvent -FilterHashtable $hash -ErrorAction Ignore
          
            If ($tempEvents) {
                $Events+= $tempEvents
            }
            $tempEvents = $null
        }
      
        If ($GroupMessages) {
            $GroupedEvents = ($Events | Group-Object Message)
          
            Foreach ($Group In $GroupedEvents) {
                $obj = New-Object psobject $Group.Group[0]
                $obj | Add-Member NoteProperty -Name 'Count' -value $Group.count
                $objCol+= $obj
            }
            $Events = $objCol
        }
    }
    End {
        $props = 'TimeCreated' , 'LevelDisplayName' , 'ProviderName' , @{ n = 'ID' ; e = {[string] $_.'ID' } } , 'TaskDisplayName' , 'Message'
        If ($GroupMessages) { $props+= 'Count' }
      
        $Out = $Events | sort TimeCreated -d | Select $props | ? {([regex]::IsMatch($_.ProviderName , $ProviderNameRegexFilter)) -and
            ([regex]::IsMatch($_.ID , $EventIDRegexFilter)) -and ([regex]::IsMatch($_.Message , $MessageRegexFilter ,[System.Text.RegularExpressions.RegexOptions]::Multiline)) }
      
        If (-not $IncludePowerShellLogs) {
            $Out = $Out | ? { $_.ProviderName -notlike '*PowerShell*' }
        }
      
        If ($OutClipBoardBySystemListSeparator) {
            $Out | ConvertTo-Csv -Delimiter $ListSeparator -NoTypeInformation | Set-Clipboard
        } Else {
            $Out
        }
    }
}

UPDATE 2017-12-21:
  • Help-Text nun vollständig und für einige Parameter geändert.
  • Weitere Beispiele angefügt
  • Anforderungen zur Ausführung + Warnung für Microsoft Server Produkte als Kommentar eingefügt
  • "EventLogLevel" als Enumeration eingebaut (Pre-PowerShell 5.0 kompatibel durch TypeDefinition)
  • Funktion zur Erstellung von RegEx-Pattern komplett überarbeitet
  • $Levels umbenannt zu $EventLogLevel
  • [ValidateNotNullOrEmpty()] für die Filter eingebaut. (Die anderen Parameter können aufgrund der Deklaration gar nicht $null oder $empty sein)
UPDATE 2018-01-05:
- $EventLogLevelInt für die Variablen-Deklaration/Wertzuordnung der EventLogLevel als Integer hinzugefügt, um die Variablen-Zuordnung pro ForEach-Step zu ersetzen. (=>minimale Ausführungszeit-Ersparnis)
 
Zuletzt bearbeitet:
Das sieht doch auf den ersten Blick schön nett aus. :)

Kleiner Tip. Du kannst in PS Enums definieren, notfalls über Add-Type. Diese sind dann in der Syntax einer der CLR-Sprachen anzugeben - C#, VB.NET, sonstwas.

Enums erlauben es, Zeichenfolgen synonym für numerische Werte zu verwenden, und wenn man in der Parameterdefinition einen Enum als Eingabetypen verwendet, stellt PS Autovervollständigung bereit für die einzelnen Werte in der Enumeration.

Dann sparst Du Dir numerische Werte für $Levels, mit denen eh keiner was anfangen kann.

Ansprechen lassen sich Enums wie statische Elemente einer Klasse, also [enumtyp]::Wert. Casts sind implizit auf String und int möglich, sodaß wenn ein Enum die Werte Ja = 0 und Nein = 1 beinhalten würde, man
Code:
 ([Antwort]:: Ja -eq 0) -eq ([Antwort]:: Ja -eq 'Ja')
erhält.


[ValidateSet(String1, ... StringN)] ermöglicht Autovervollständigen für blanke Strings, wenn ein Parameter nur bestimmte Werte annehmen darf und es aber kein Enum sein soll oder muß.

Übrigens ist das mit dem -ErrorAction immer so eine Sache, wenn man da nicht grad Stop hinschreibt. Denn dann werden alle Fehler einfach ignoriert, die auftreten können, auch die, die man eigentlich gar nicht haben wollte.

Besser: Try{...}Catch {...} um einen Befehl bauen und wenn der -ErrorAction versteht, das angeben und mit Stop versehen. (Ansonsten $ErrorActionPreference auf Stop setzen -- ebenfalls ein Enum, [System.Management.Automation.ActionPreference] ).

Catch kann unter Angabe einer Ausnahmeklasse nur diejenigen Ausnahmen einsammeln, die man tatsächlich behandeln wollte und den Rest durchlassen (oder per catch-all im Sinne eines "case default"). Fehler, die nicht auftreten SOLLEN, werden dann wieder gemeldet bzw sind gesondert behandelbar - Beispiel: wenn Get-Item ein ItemNotFoundException wirft, kann man per Catch-Block für genau diese Ausnahme das einfangen und kann dann sagen, okay wenn die Datei nicht da war, na dann leg ich sie eben jetzt hier an.
 
Danke schon mal für das Feedback ;)

Hab mich jetzt mal ein bisschen ENUMs in Verbindung mit Funktionen angeschaut.
Anfangs dachte ich, ich sei zu blöd das in eine Funktion zu implementieren.

Grund:
Sobald ich eine Funktion mit einem vorher definierten ENUM baue, fehlt die komplette Syntax bzw. wird nicht angezeigt, wenn ich im Skriptbereich in der PS-ISE die Funktion verwenden will.
Hab es dann später einfach mal in der Konsole probiert und voila, da funktioniert es tadellos.

Ist das generell so oder ein aktueller Bug?


Beispiel aus dem WWW:

Code:
Enum WineSweetness { 
	VeryDry 
	Dry 
	Moderate 
	Sweet 
	VerySweet 
} 


Function Get-Wine { 
	[CmdletBinding()] 
	Param (
		[parameter(Mandatory = $false)] 
		[WineSweetness[]] $SweetnessValue 
	) 
	
	Process { 
		write-host "Selected Names: $($SweetnessValue -join ',')" 
		write-host "Selected Values: $($SweetnessValue.value__ -join ',')" 
	} 
} 

########################################################################## 
# Beispiel: 
Get-wine -SweetnessValue Dry , Moderate

In das Scriptpane in der PowerShell ISE einfügen, einmal leer durchlaufen/starten lassen, dann versuchen die Funktion Get-Wine im Skriptbereich und danach in der Konsole zu verwenden.
Mein Ergebnis:
-> Scriptpane -> Syntax wird nicht angezeigt
-> Konsole -> Syntax wird angezeigt
 
Einmal das Modul bzw den zugehörigen Datentypen laden und dann kennt das Scriptpane den auch. Vorher leider nicht. Wenn Du das in ein Modul stopfst, kannst Du das natürlich auch einfach importieren.

Wobei ich grad nicht ganz sicher bin, ab welcher PS-Version enums nativ unterstützt werden. Wenn Du ältere PS-Versionen auch noch unterstützen willst, müßtest Du das per Add-Type machen.


Hier eine Beispielimplementierung auf Add-Type - Basis:

Code:
function Add-Enum
 {
 [CmdletBinding()] # Older PS do not support PositionalBinding-Attribute to cmdletbinding()
 Param
 (
 [Parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 [string]$TypeName,

 [Parameter(Mandatory=$true)]
 [ValidateNotNullOrEmpty()]
 # Validate: the values for each enum must be of ValueType; if they're not we cannot pass them through here.
 # (They must also be unique.)
 [validatescript({ $_.GetEnumerator() | % {$_.Value -is [System.ValueType]} })]
 [System.Collections.IDictionary]$TypeDefinition,

 [Parameter(Mandatory=$false)]
 [switch] $BailIfExists = $false

 )

 # As this string is to be evaluated with -f, curly braces must be doubled (to escape them).

[string]$enumDefTpl = @'
public enum {0}
{{{1}
}}
'@;
[string]$crlf = "`n";
[string]$strTypeDefTpl = $crlf + '{0} = {1},';
[string]$strTypeDef = '';

$TypeDefinition.GetEnumerator() | 
 % {
 
        $strTypeDef += $strTypeDefTpl -f $_.Key, $_.Value;
   }

# Assert: At this point, we have a complete enum definition to feed to Add-Type; HOWEVER
# there is a ',' even after the last entry.
# It does not seem to cause problems, but we're going to clear it out regardless.

[int]$idxOfLastComma = $strTypeDef.LastIndexOf(',');

$strTypeDef = $strTypeDef.Remove($idxOfLastComma,1);

 [string]$newTypeDefinition = $enumDefTpl -f $TypeName, $strTypeDef;

 # Assert: That comma after the last entry is now gone.
 
 # So, we're safe now to Add-Type the type definition... unless of course there's more problems, such as pre-existing type names:
 
    Try
    {
        Add-Type -TypeDefinition $newTypeDefinition -ErrorAction Stop;
    }
    Catch
    {
    $ex = $_ -as [System.Management.Automation.ErrorRecord];
    # Unfortunately, if type exists we get a generic NET CLR error (8013 1500).

    # However, the message returned still gives the right thing, so "type exists" *should* hide somewhere in encoded form.

     # For now, feed message back to user with the CAVEAT we might not be capturing errors that SHOULD have been captured.

     # Also, if -BailIfExists has been given, error out. This is to alleviate security concerns.

     if($BailIfExists -eq $false)
     {
        Write-Warning ( '{0}' -f $_.Exception.Message );
     } # don't bail if type exists
     else # well, DO bail
     {
        throw  $_;
     }
        
    } 
 }

Verwendung:
Code:
Add-Enum -TypeName EnumType -TypeDefinition @{'A' = 0; 'B' = 1}

PS $> [EnumType]::A
A
PS $> [EnumType]::A.value__
0

-TypeDefinition erwartet irgendeine Wörterbuchimplementierung ([hashtable] ist eine) mit Key/Valuepaaren von Schlüsselname/Wert (siehe Beispiel). Könnte man sicher auch anders machen, aber so oft legt man Enums ja nicht fest, daß die extra Schreibarbeit nicht stören sollte. Und wenn man noch einen SwitchParameter -Flags einbaut und auf dessen Basis dem erstellten Enum ein [FlagsAttribute] zuweist, dann funktioniert das als Bitfeld.

Note: man kann mit dieser Implementierung für -TypeName auch irgendwas Ungültiges angeben, eine entsprechende Validierung fehlt in der Parameterdeklaration. Könnte man aber problemlos hinzufügen durch ein Validate"irgendwas",was nur noch Buchstaben durchläßt, sodaß nicht versucht werden kann, zB ein Leerzeichen als Name festzulegen (der Versuch würde spätestens beim eigentlichen Add-Type eine Ausnahme werfen wegen fehlerhafter Syntax).
 
RalphS schrieb:
Einmal das Modul bzw den zugehörigen Datentypen laden und dann kennt das Scriptpane den auch. Vorher leider nicht. Wenn Du das in ein Modul stopfst, kannst Du das natürlich auch einfach importieren.

Geht sogar viel leichter. Gerade durch Zufall / rumprobieren rausbekommen. ;)
Der Scope / Access Level PUBLIC hat gefehlt:
Code:
Public Enum WineSweetness { 
	VeryDry 
	Dry 
	Moderate 
	Sweet 
	VerySweet 
}

Hab meine Funktion Get-Eventlog2 oben nun angepasst.
Zudem wird nun auch PowerShell 5.0 benötigt, dementsprechend nun auch ein Hinweis im Code.
 
Zuletzt bearbeitet:
Du solltest das nicht auf einer so neuen Powershell basieren lassen. Denn dann kannst du das auf den meisten Servern nicht einsetzen. Exchange zB setzt die Powershell des Betriebssystems voraus und kann nicht aktualisiert werden. Da bist du teilweise auf die PS4 angewiesen und deine dort sicherlich nützliche Routine ist wertlos. PS3 täte ich als unterste Version nehmen.

Und allgemein zu deinen Routinen, wenn du das weiter verfolgen willst, bastle dir das ganze System als ein Modul und binde das zentral über eine profile.ps1 ein. Bei der ExecutionPolicy RemoteSigned geht das dann mit $PSModulePath hinzugefügten Modulen ganz gut in Domänen. Zudem hast du alles gut organisiert um das einfach auszubauen.
 
morcego schrieb:
Du solltest das nicht auf einer so neuen Powershell basieren lassen. Denn dann kannst du das auf den meisten Servern nicht einsetzen. Exchange zB setzt die Powershell des Betriebssystems voraus und kann nicht aktualisiert werden. Da bist du teilweise auf die PS4 angewiesen und deine dort sicherlich nützliche Routine ist wertlos. PS3 täte ich als unterste Version nehmen.

Und allgemein zu deinen Routinen, wenn du das weiter verfolgen willst, bastle dir das ganze System als ein Modul und binde das zentral über eine profile.ps1 ein. Bei der ExecutionPolicy RemoteSigned geht das dann mit $PSModulePath hinzugefügten Modulen ganz gut in Domänen. Zudem hast du alles gut organisiert um das einfach auszubauen.

Ja für Exchange sollte ich vielleicht runter gehen mit der Version.

Das Ganze ist bei mir (private) eh immer per Modul eingebunden (Bei anderen mach ich das natürlich auch; jedoch nicht auf OneDrive).
Hab hierfür damals einfach folgendes gebastelt. Danach reicht es einfach die Module ins entsprechende Verzeichnis zu verschieben. Sind dann automatisch verfügbar.

Microsoft.PowerShellISE_profile.ps1
Code:
$MyModulePath = "$env:UserProfile\OneDrive\Windows\Powershell\Modules"

$Modules = Get-ChildItem "$MyModulePath" -Recurse -Filter "*.psm1"
Import-Module $Modules.FullName -WarningAction Ignore

$BuffSizeScriptBlock = { $host.UI.RawUI.BufferSize = [System.Management.Automation.Host.Size]::new(8096 , 8096) }

If ($psise) {
    $psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Optimize-PScript' , { $psISE.CurrentFile.Editor.Text = (Optimize-PScript $psISE.CurrentFile.Editor.Text) } , 'F6') | out-null
    $psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('RemVars' , { Get-Variable | Remove-Variable -ErrorAction Ignore } , 'F4') | out-null
    $psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Help-File' , { Help-File } , 'Shift+F1') | out-null
    $psise.CurrentPowerShellTab.AddOnsMenu.Submenus.Add('Raise BufferSize' , $BuffSizeScriptBlock , 'Ctrl+0') | Out-Null
}

Microsoft.PowerShell_profile.ps1 (Verweis auf "Microsoft.PowerShellISE_profile.ps1", um nicht alles doppelt zu pflegen)
Code:
. (join-path (Split-Path $PSCommandPath -Parent) 'Microsoft.PowerShellISE_profile.ps1' )



Eine Herausfoderung hatte ich eben noch.
Eine dynamische Generierung eines RegexPattern.
Als Eingabe sollten * und ? dienen.
Ausgabe sollte dann Regex-Komform sein.

Dazu müssen ja [*] durch [.*] und [?] durch [.] ersetzt werden.
Soweit so gut. Nun müssen ja aber auch normale Satzzeichen escaped werden.
Könnte hier mal jemand drüber schauen, ob ich alles abgegriffen habe.
Ja, die Ausgabe enthält haufenweise Klammern, aber funktioniert trotzdem, außer ich hab natürlich ein Zeichen vergessen zu escapen.

Code:
Function Create-WildcardAdjustedRegexTerm([string[]] $InputString) {
    $Result = @()
   
    $WildcardCharacters = @{
        EscapedAsteriskWildcard = @{ Original = '*' ; Escaped = '\*' ; Regex = '.*' ; TemporaryEscapeCharacter = '#%EscapedAstersik%#' }
        EscapedMatchOneWildcard = @{ Original = '?' ; Escaped = '\?' ; Regex = '.' ; TemporaryEscapeCharacter = '#%EscapedMatchOne%#' }
    }
   
    $Exclusions = @{
        DotExclusion = @{ Original = '.' ; Escaped = '\.' }
        DollarExclusion = @{ Original = '$' ; Escaped = '\$' }
        CircumflexExclusion = @{ Original = '^' ; Escaped = '\^' }
        PlusExclusion = @{ Original = '+' ; Escaped = '\+' }
    }
   
    Foreach ($InputStr In $InputString) {
        $FilterString = $InputStr
       
        # Exclude other characters (e.g. dot)   
        $FilterString = $FilterString.replace($Exclusions.DotExclusion.Original , $Exclusions.DotExclusion.Escaped)
        $FilterString = $FilterString.replace($Exclusions.DollarExclusion.Original , $Exclusions.DollarExclusion.Escaped)
        $FilterString = $FilterString.replace($Exclusions.CircumflexExclusion.Original , $Exclusions.CircumflexExclusion.Escaped)
        $FilterString = $FilterString.replace($Exclusions.PlusExclusion.Original , $Exclusions.PlusExclusion.Escaped)
       
        # Change wildcard characters   
        $FilterString = $FilterString.replace($WildcardCharacters.EscapedAsteriskWildcard.Escaped , $WildcardCharacters.EscapedAsteriskWildcard.TemporaryEscapeCharacter)
        $FilterString = $FilterString.replace($WildcardCharacters.EscapedMatchOneWildcard.Escaped , $WildcardCharacters.EscapedMatchOneWildcard.TemporaryEscapeCharacter)
       
        $FilterString = $FilterString.split($WildcardCharacters.EscapedAsteriskWildcard.Original)
        $FilterString = "(" + ($FilterString -join ")$($WildcardCharacters.EscapedAsteriskWildcard.Regex)(") + ")"
       
        $FilterString = $FilterString.Split($WildcardCharacters.EscapedMatchOneWildcard.Original)
        $FilterString = "(" + ($FilterString -join ")$($WildcardCharacters.EscapedMatchOneWildcard.Regex)(") + ")"
       
        $FilterString = $FilterString.replace($WildcardCharacters.EscapedAsteriskWildcard.TemporaryEscapeCharacter , $WildcardCharacters.EscapedAsteriskWildcard.Escaped)
        $FilterString = $FilterString.replace($WildcardCharacters.EscapedMatchOneWildcard.TemporaryEscapeCharacter , $WildcardCharacters.EscapedMatchOneWildcard.Escaped)
       
        $Result+= "($FilterString)"
    }
    Return $Result -join "|"
}


$Text = 'Name resolution for the name events.gfe.nvidia.com timed out after none of the configured DNS servers responded. Do you want to confirm? Timestamp: Donnerstag, 21. Dezember 2017 00:00:00+0'
$RegexInput = 'Name resolution for the name events.gfe.nvidia.com timed out * none ?? the configured DNS servers responded. Do you want to confirm\? Timestamp: Donnerstag, 21. Dezember 2017 00:00:00+0'

$RegexPattern = Create-WildcardAdjustedRegexTerm $RegexInput

[regex]::Match($Text,$RegexPattern)
Ergänzung ()

Nachtrag:
Hab den Code angepasst und eine Warnung als Kommentar eingefügt, da es mir als ehemaliger SysAdmin bekannt ist, dass WMF Updates und Windows Server Services sehr schlechte Freunde sind. ;)

Hat jemand eine Idee, wie man Strict eine alte Version (z. B. PowerShell 3.0) emulieren kann, ohne verschiedene Versionen / Systeme parat zu haben?
Hab nämlich noch irgendwas im Hinterkopf, dass ConvertTo-Csv früher andere Parameter hatte bzw. es mittlerweile neue gibt.

Alternativ: Kann jemand mit PSVersion 3.0 das Ganze mal testen?
 
Zuletzt bearbeitet:
Gar nicht. Version 2 geht - dazu an PowerShell.exe den Parameter -Version 2 oder -Version 2.0 übergeben, dann wird nur PS v2 kompatible Syntax zugelassen UND das Backend auf .NET 2.x festgelegt (nur interessant, wenn Du .NET-Objekte einfügst und die sind in einer Version da und in einer anderen nicht).

Einbinden tu ich in $Profile nix weiter. Was ich aber mach ist die Möglichkeiten von PS zu nutzen und ein PSDrive anzulegen. Das verweist dann auf den Pfad mit den Modulen. Dann muß das nur noch in $Env:PsModulePath aufgenommen werden. Sind zusammen zwei Zeilen.

Man muß dann nur noch aufpassen, daß wenn es irgendwo um die Übergabe von Pfaden insbesondere aus der PS raus geht, daß man dann zB mit $PWD.ProviderPath oder mit (Get-PSDrive).Root an den verlinkten "Originalpfad" ran muß. cmd.exe versteht sowas wie "Ablage:\Datei.txt" schließlich nicht.

RE: PS version 5.x (ich verwend 5.1 hier) hab ich bisher keine Probleme gehabt. Wenn ich was portabler brauche muß ich halt schauen ob a) die neuere PS auszurollen geht und wenn nicht, halt b) an PS v2 Syntax halten. Die ist zwar in vielerlei Hinsicht doof, aber dafür geht es dann.

.NET-Objekte verwenden können beide, und - insbesondere - .NET-Quellcode zur Laufzeit einbinden und kompilieren auch. Damit umgeht man das leidige Problem, daß wieder irgendwas nicht so richtig gefunden werden kann. Add-Type kann viel - der Name ist ein bißchen ungeschickt gewählt; man kann damit auch eine komplette Klassendefinition samt Methoden und allem bauen und einbinden, nicht nur irgendwelche Datentypen.

Allerdings versteht PS kein C# in Version 6. Man muß sich an die "klassischen" Regeln halten. ZB Lambda-Konstrukte mit => gehen zwar unter C#, nicht jedoch als Quellcode für PowerShell.



RE: regex-to-regex Übersetzer würd ich das spontan als Parser implementieren wollen:

? haben (wie *) auch im Ausgabesegment (regex) eine Bedeutung. Wenn man es richtig machen will, legt man also eine Konstante an wo a) das Escapezeichen für die Eingabe und b) das Escapezeichen für die Ausgabe drinsteckt. Für PS kann man das natürlich auch per Parameter übergeben. Ergo:

- Zeichen lesen
- Was ist das fürn Zeichen?
> "Eingabe-Escape" => Aha, nächstes Zeichen hat regulären Status bezogen auf die Eingabe. Deshalb Flag setzen, daß das nächste Zeichen nicht "übersetzt" werden soll. Fertig für dieses Zeichen.
> * oder ? =>
## War das Eingabe-Escape-Flag gesetzt?
==== Falls ja, ist das Ausgabe-Escape-Zeichen zu schreiben und dann entsprechend der Eingabe den * oder das ? dazu. Danach ist das Eingabe-Escape-Flag wieder zurückzusetzen.
==== Falls nicht, muß wie bereits festgestellt für * ein ".*" da hin und fürs ? ein einzelner Punkt.

> Dann muß gefragt werden, was im Eingabeformat vorkommen kann, das im Ausgabeformat eine besondere Bedeutung hat und im Eingabeformat aber nicht: ^ - {} [ ] ( ) beispielsweise. Ist also mein eben gelesenes Zeichen so eins, dann muß ich da wieder das Ausgabe-Escape-Zeichen davor schreiben.


>> Das mach ich mit jedem Input-Zeichen. Irgendwann bin ich dann fertig und hab meine Eingabe vollständig in das neue Format überführt. Laufzeit ist linear abhängig von der Eingabe, dauert also für jedes zusätzliche Eingabezeichen eine Zeiteinheit mehr.

Was übrigbleibt war ein "bedeutungsloses" Zeichen -- Eingabetext -- welcher einfach aus der Eingabe in die Ausgabe kopiert wird.


Wobei ich da eher der Meinung bin, daß man sich das genausogut sparen könnte. Einfach deswegen, weil regex so sehr viel mehr ermöglichen als ? oder * im GLOB-Format. Hat man also das zweitere, ist man vermutlich besser beraten, wenn man das einmal in einen vernünftigen Regex umbaut. Ansonsten bekäme man für Hansi**** bissel komische Ausdrücke und, schlimmer, für Hansi?? umgebungsabhängige Fehlinterpretationen - soll es nun ein Hansi sein mit keinem, einen oder zwei zusätzlichen Zeichen nach dem i? Auf der anderen Seite wird sich "Hansi.*.*.*.*" zwar wie erwartet verhalten (beliebige Zeichenfolge, die mit "Hansi" losgeht) aber es ist trotzdem ganz offensichtlich kein Regex, den man irgendjemandem vorlegen kann.

Besonders lustig wird es aber dann, wenn dann Eingabezeichenfolgen kommen, in denen versucht wurde, etwas abzubilden, was mit Regex vergleichsweise banal gegangen wäre. Nur mal als syntaxfremdes Beispiel: schreib ich (0|1|2|3|4|5|6|7|8|9)?(0|1|2|3|4|5|6|7|8|9) da hin oder schreib ich [0-9]{1,2} ? Ein simpler Übersetzer versagt erstmal bei solchen Konstruktionen, und während man einen Parser zwar bauen kann, daß der das hinbekommt, funktioniert das dann nicht mehr linear: stattdessen müßte der wie oben "vorübersetzte" Ausdruck noch einmal eingelesen werden in zB eine Zustandsmaschine, die den Regex syntaktisch abbildet, und dann müßten zusätzlich Regeln definiert werden, was in welcher Form vereinfacht werden kann. Dann könnte zB gesagt werden, okay ich hab ein ( und dann hab ich ein einzelnes Zeichen und danach ein | ... hey, das sieht aus wie eine Zeichenklasse. Probieren wir erstmal ( zu schreiben und dann ein [ und dann die Zeichen alle, und zwar solange, bis a) in der Eingabe die ) folgt (dann kann ich mein ] und mein ) wieder schließen) ODER mehr als nur ein einzelnes Zeichen zwischen zwei | stand, in welchem Fall wir keine Zeichenklasse, sondern eine Alternativauswahl haben und dann müßten wir unser Zwischenresultat verwerfen und die ursprüngliche Eingabe anders bewerten (oder einfach übernehmen).




Daher, nur so als Vorschlag, auf den Du sicher selber auch schon gekommen bist: nimm in Zeile #49 einfach $Text -like $RegexInput .
 
Zuletzt bearbeitet von einem Moderator:
Ich mache das etwas anders mit der profile. Die kommt angepasst in "explorer $PSHome"
Code:
<#
    .Synopsis
        Erweitert die lokale Powershell Session um die verfügbaren Module
#>

[string]$MyModuls = "\\server\path\WindowsPowerShell"

if ( Test-Path -path $MyModuls\Modules) {
    $env:PSModulePath += ";$MyModuls\Modules"
    Get-ChildItem -Path $MyModuls\Modules -Directory | ForEach-Object { 
        Import-Module $_ -ErrorAction SilentlyContinue

        if (Get-Module -ListAvailable -Name $_.Name) {
            Write-Host $_.Name -ForegroundColor Green
        } else {
            Write-Host $_.Name.ToString() "konnte nicht geladen werden" -ForegroundColor Red
        }
    }
    $env:Path += ";$MyModuls\Skripts"
    }
else {
    $env:PSModulePath += ";$env:userprofile\Documents\MyPowershell\Modules"
    Get-ChildItem -Path $env:userprofile\Documents\MyPowershell\Modules -Directory | ForEach-Object { 
	Import-Module $_
	
	if (Get-Module -ListAvailable -Name $_.Name) {
            Write-Host $_.Name -ForegroundColor Green
        } else {
            Write-Host $_.Name.ToString() "konnte nicht geladen werden" -ForegroundColor Red
        }
    }
    $env:Path += ";$env:userprofile\Documents\MyPowershell\Skripts"
}

in der psm werden dann alle ps1 Dateien geladen.

Code:
# alle Skripts im Rootordner des Moduls finden:
$StartLoad = Get-Date

Get-ChildItem -Path $PSScriptRoot -filter *.ps1 |
	Where-Object { $_.Extension -eq '.ps1' } |
	ForEach-Object {
		$start = Get-Date
  		. $_.FullName
  		$ende = Get-Date
  		# hat der Ladevorgang verdächtig lange gedauert?
  		$dauer = ($ende - $start).TotalSeconds

		Write-Verbose ('Ladezeit des Scripts "{0}": {1} Sekunden.' -f $_.Name, $dauer)

 		if ($dauer -gt 2) {
			Write-Warning ('Sehr lange Ladezeit des Scripts "{0}": {1} Sekunden.' -f $_.Name, $dauer)
			Write-Warning 'Dies ist ein Hinweis, dass im Skript unbeabsichtigte Befehle ausgeführt werden.'
		}
	}

Get-ChildItem -Path $PSScriptRoot -filter *.dll | Foreach-Object { Import-Module $_.Fullname}

# alle Format-Daten im Rootordner des Moduls finden:
Get-ChildItem -Path $PSScriptRoot -Filter *format.ps1xml |
	Where-Object { $_.Extension -eq '.ps1xml' } |
	Foreach-Object { Update-FormatData -PrependPath $_.FullName }

$EndLoad = Get-Date
$dauerLoad = ($EndLoad - $StartLoad).TotalSeconds
if ( $dauerLoad -gt 0.25) { Write-Warning ('Gesamtladedauer betrug {0} Sek, Grenzwert ist 0,25 Sek.' -f $dauerLoad) }

Die Prüfung auf die ISE mache ich dann in der zugehörigen ps1 und klinke dann meinen Kram ein.
Alles was ich dann machen muss ist die profile.ps1 auf die Rechner ausrollen und die ExecutionPolicy setzen. Beides geht per GPO. Damit musst du nichts doppelt vorhalten und neue Rechner in der Domäne werden sofort versorgt.

Bei den Skripten und Modulen hab ich mich am Powershell 3 Buch orientiert, der hatte da alles fertig vorbereitet. Paar Details habe ich noch angepasst da ich die unpassend fand.
 
RalphS schrieb:
RE: regex-to-regex Übersetzer würd ich das spontan als Parser implementieren wollen:

? haben (wie *) auch im Ausgabesegment (regex) eine Bedeutung. Wenn man es richtig machen will, legt man also eine Konstante an wo a) das Escapezeichen für die Eingabe und b) das Escapezeichen für die Ausgabe drinsteckt. Für PS kann man das natürlich auch per Parameter übergeben. Ergo:

- Zeichen lesen
- Was ist das fürn Zeichen?
> "Eingabe-Escape" => Aha, nächstes Zeichen hat regulären Status bezogen auf die Eingabe. Deshalb Flag setzen, daß das nächste Zeichen nicht "übersetzt" werden soll. Fertig für dieses Zeichen.
> * oder ? =>
## War das Eingabe-Escape-Flag gesetzt?
==== Falls ja, ist das Ausgabe-Escape-Zeichen zu schreiben und dann entsprechend der Eingabe den * oder das ? dazu. Danach ist das Eingabe-Escape-Flag wieder zurückzusetzen.
==== Falls nicht, muß wie bereits festgestellt für * ein ".*" da hin und fürs ? ein einzelner Punkt.

> Dann muß gefragt werden, was im Eingabeformat vorkommen kann, das im Ausgabeformat eine besondere Bedeutung hat und im Eingabeformat aber nicht: ^ - {} [ ] ( ) beispielsweise. Ist also mein eben gelesenes Zeichen so eins, dann muß ich da wieder das Ausgabe-Escape-Zeichen davor schreiben.


>> Das mach ich mit jedem Input-Zeichen. Irgendwann bin ich dann fertig und hab meine Eingabe vollständig in das neue Format überführt. Laufzeit ist linear abhängig von der Eingabe, dauert also für jedes zusätzliche Eingabezeichen eine Zeiteinheit mehr.

Was übrigbleibt war ein "bedeutungsloses" Zeichen -- Eingabetext -- welcher einfach aus der Eingabe in die Ausgabe kopiert wird.


Wobei ich da eher der Meinung bin, daß man sich das genausogut sparen könnte. Einfach deswegen, weil regex so sehr viel mehr ermöglichen als ? oder * im GLOB-Format. Hat man also das zweitere, ist man vermutlich besser beraten, wenn man das einmal in einen vernünftigen Regex umbaut. Ansonsten bekäme man für Hansi**** bissel komische Ausdrücke und, schlimmer, für Hansi?? umgebungsabhängige Fehlinterpretationen - soll es nun ein Hansi sein mit keinem, einen oder zwei zusätzlichen Zeichen nach dem i? Auf der anderen Seite wird sich "Hansi.*.*.*.*" zwar wie erwartet verhalten (beliebige Zeichenfolge, die mit "Hansi" losgeht) aber es ist trotzdem ganz offensichtlich kein Regex, den man irgendjemandem vorlegen kann.

Besonders lustig wird es aber dann, wenn dann Eingabezeichenfolgen kommen, in denen versucht wurde, etwas abzubilden, was mit Regex vergleichsweise banal gegangen wäre. Nur mal als syntaxfremdes Beispiel: schreib ich (0|1|2|3|4|5|6|7|8|9)?(0|1|2|3|4|5|6|7|8|9) da hin oder schreib ich [0-9]{1,2} ? Ein simpler Übersetzer versagt erstmal bei solchen Konstruktionen, und während man einen Parser zwar bauen kann, daß der das hinbekommt, funktioniert das dann nicht mehr linear: stattdessen müßte der wie oben "vorübersetzte" Ausdruck noch einmal eingelesen werden in zB eine Zustandsmaschine, die den Regex syntaktisch abbildet, und dann müßten zusätzlich Regeln definiert werden, was in welcher Form vereinfacht werden kann. Dann könnte zB gesagt werden, okay ich hab ein ( und dann hab ich ein einzelnes Zeichen und danach ein | ... hey, das sieht aus wie eine Zeichenklasse. Probieren wir erstmal ( zu schreiben und dann ein [ und dann die Zeichen alle, und zwar solange, bis a) in der Eingabe die ) folgt (dann kann ich mein ] und mein ) wieder schließen) ODER mehr als nur ein einzelnes Zeichen zwischen zwei | stand, in welchem Fall wir keine Zeichenklasse, sondern eine Alternativauswahl haben und dann müßten wir unser Zwischenresultat verwerfen und die ursprüngliche Eingabe anders bewerten (oder einfach übernehmen).
Du hast mich gerade auf die eckigen Klammern gebracht ..
Werden durch RegEx ja auch interpretiert :O

Ich glaub ich bau die Funktionalität wieder aus bzw. beschränk sie auf Event ID und Event Provider.
"Like" würde es auch tun. Da geht halt dann nur Asterisk, aber ich erspar mir die RegEx-konforme Interpretation.
 
Siehst, eins fällt mir grad noch ein: ISESteroids.

Kostet aber was. Knapp 120 Euronen für den Einzelanwender, dann darf man das auf 3 PCs gleichzeitig verwenden (welche ist egal) einschließlich auf Arbeit. Bedingung: Man muß das auch wirklich selber bezahlen und darf sich das nicht vom Chefchen ersetzen lassen. (Unternehmenslizenzen gibt's aber auch.)

Damit kann man dann anhaken, nach welchem PS-Schema der Code geprüft werden soll.
 
RalphS schrieb:
Siehst, eins fällt mir grad noch ein: ISESteroids.

Kostet aber was. Knapp 120 Euronen für den Einzelanwender, dann darf man das auf 3 PCs gleichzeitig verwenden (welche ist egal) einschließlich auf Arbeit. Bedingung: Man muß das auch wirklich selber bezahlen und darf sich das nicht vom Chefchen ersetzen lassen. (Unternehmenslizenzen gibt's aber auch.)

Damit kann man dann anhaken, nach welchem PS-Schema der Code geprüft werden soll.
ISESteroids hab ich vor glaub ca. 2 Jahren schon probiert, aber ehrlich gesagt schon lange nicht mehr einem Test unterzogen.
Fand's damals nicht beeindruckend. Aber werde es mal wieder versuchen. Hab seitdem eh kein großes PowerShell-AddIn in dieser Hinsicht probiert, sondern alles in Eigenentwicklung versucht. Besonders wenn es kompiliert war und somit keine einfache Möglichkeit der Anpassung via (PowerShell)Editor zuließ.
 
Ja, deswegen sag ich ja auch. Das muß man sich vorher echt angucken, weil 120 Euronen fürn PowerShell-Modul was einem nicht viel nützt ist albern.

Wobei sich natürlich in den Jahren schon was getan hat. 10 Tage hat man zum Testen, und bei ISESteroids heißt das wirklich 10 Tage, also sprich, Du darfst an zehn verschiedenen Tagen das testen. Mit 10 Tagen ist NICHT "heute, morgen und dann noch acht Tage" gemeint. Von daher -denk ich- ist das relativ okay mit den "10".

Hatte mich vor ner Weile selber mal umgeschaut gehabt was es da so gibt als PowerShell-Editor. Also abgesehen von der ISE. Die rufen alle Preise auf, die jenseits von Gut und Böse sind. PowerShell Studio? $400 für die Einzelplatzlizenz. So gesehen sind die Steroids schon wieder billig. ^^

Oder anders gesagt, das ist wirklich nur dann sinnvoll, wenn man ausreichend viel mit PS arbeitet UND wenn einem so eine Umgebung (bzw Erweiterung für eine) ausreichend viel Arbeit abnimmt/erleichtert, daß man das auch wieder rauskriegt.
 
Ja, das fehlt der ISE wirklich. So viel wie sie kann, aber sowas letztlich albern Banales... das fehlt. :/

Danke für den Link und für den Quelltext, schau ich mir gleich mal an. :)
 
Zurück
Oben