Private/Wissen/B_Basic/B41_Typen.ps1

<#
 
# PowerShell und .NET Typen
 
Die Bedeutung und Verwendung von Typen in der PowerShell.
 
- **Hashtags** Werttyp Referenztyp Hashtable int long double decimal datetime array hex xml StringData Email single ScriptBlock
 
- **Version** 2020.05.24
 
#>


[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '')]
param()

# ! Sämtliche Typen der PowerShell stammen aus .NET oder wurden von solchen Typen abgeleitet.

# ! In der PowerShell gibt es für einige Typen Aliase und stellen ein Synonym zum .NET-Typen dar, z.B.:

[int] # Alias für:
[Int32] # ! KEIN Alias
[System.Int32]

[long]
[Int64]
[System.Int64]

# ! Die wichtigsten PowerShell Typen in der Übersicht:

[PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Get.Keys | Sort-Object | ForEach-Object { "[$_]" }

#region String (Text)

# ! String-Wert wird der Variable $Ort zugewiesen:

$Ort = 'Würzburg'

# ! Excl. Variablen-Auflösung:

'Begrüßung $Ort!'

# ! Incl. Variablen-Auflösung:

"Hallo $Ort!"

# ! Incl. Ausdruck-Auflösung:

"Heute ist $(Get-Date -Format 'dddd')!"
$Ort.Length
"Der Ortsname $Ort ist $($Ort.Length) Zeichen lang!"

# ! String-Block incl. Sonderzeichen:

$StringBlockBeispiel = @"
Dieser String-Block kann "Doppelte Anführungszeichen",
Zeilenumbrüch oder sonstige Steuer Zeichen wie ein Tabulatur enthalten!
 
- Die erste Zeile MUSS mit @" enden!
- Die letzte Zeile muss mit "@ beginnen!
 
Erstellt in "$Ort", am $([DateTime]::Today)
"@


# ! Escape characters, Delimiters and Quotes (http://ss64.com/ps/syntax-esc.html):

"Hallo `n Köln!"
"Hallo `r`n Köln!"
"Hallo `t Köln!"

# TIPP - Sonderzeichen in der PowerShell:
Get-Help -Name 'about_Special_Characters' -ShowWindow

#endregion

#region Byte, Int16, UInt16, int, Int32, UInt32, long, Int64 und UInt64 (Ganzzahl)

# ! Verwendung:

$Ganzzahl = 10 # ? Default: Int32, int
$Ganzzahl = [byte]10
$Ganzzahl = [long]10

# ! Wertbereiche:

[Byte]::MinValue
[Byte]::MaxValue

[Int16]::MinValue
[Int16]::MaxValue

[UInt16]::MinValue
[UInt16]::MaxValue

# ? Alias: int
[Int32]::MinValue
[Int32]::MaxValue

[UInt32]::MinValue
[UInt32]::MaxValue

# ? Alias: long
[Int64]::MinValue
[Int64]::MaxValue

[UInt64]::MinValue
[UInt64]::MaxValue

# ! Hexadezimale Werte:

'{0:X}' -f 255
# oder:
0xFF
# oder:
[Convert]::ToByte(0xFF)
# oder:
255 | Format-Hex

#endregion

#region Decimal (Dezimalzahlen), Double, Single (Gleitkommazahlen)

# ! Verwendung:

$Gleitkommazahlen = 10.42 # ? Default: double
$Gleitkommazahlen = [Decimal]10.42

# ! Wertbereiche:

[Decimal]::MinValue
[Decimal]::MaxValue
[Decimal] | Get-Member -Static

[Double]::MinValue
[Double]::MaxValue
[Double] | Get-Member -Static

[Single]::MinValue
[Single]::MaxValue
[Single] | Get-Member -Static

#endregion

#region DateTime, DateTimeOffset (Datum und Zeit)

# ! Wertbereiche:

[DateTime]::MinValue
[DateTime]::MaxValue
[DateTime] | Get-Member -Static

# incl. Zeitzone +/-14h
[DateTimeOffset]::MinValue
[DateTimeOffset]::MaxValue
[DateTimeOffset] | Get-Member -Static

# ! Verwendung:

$DatumZeit = [datetime]'04/23/2020' # ? Default: en-US => MM/dd/yyyy
$DatumZeit = [datetime]'01.10.2020' # ? Default: en-US => MM/dd/yyyy

# ? String in DateTime konvertieren:

# OS Ländereinstellung:
[Convert]::ToDateTime('1.10.2019')

# ISO:
[Convert]::ToDateTime('2019-10-1')

# en-US:
[Convert]::ToDateTime('10/1/2019', [CultureInfo]::new('en-US'))

# de-DE:
'01.10.2020'.ToDateTime([CultureInfo]::new("de-DE"))

# yyyyMMdd:
[DateTime]::ParseExact('20200126', 'yyyyMMdd', $null)

# Benutzereingabe anhand dessen OS-Sprache parsen:
$ci = Get-Culture
$benutzereingabe = Read-Host -Prompt "Bitte Datum eingeben ($($ci.DateTimeFormat.ShortDatePattern))"
$benutzereingabe.ToDateTime($ci)

# ? Unix-Time:

$unixtime = Get-ItemProperty -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion' | Select-Object -ExpandProperty 'InstallDate'
$span = New-TimeSpan -Seconds $unixtime
$start = [datetime]'1970-01-01'
$start + $span

# ? ISO Wochennummer:

Get-Date -UFormat '%V'

# ? (Aktuelles) Datum in einem besonderem Format zurück geben:

Get-Date -Format 'dddd, d. MMMM yyyy'
'{0:dddd, d. MMMM yyyy}' -f [datetime]'2020-05-13'

$heute = Get-Date
'Heute ist der {0:dd}., Morgen ist {1:ddd}. und Übermorgen ist {2:dddd}.' -f $heute, $heute.AddDays(1), $heute.AddDays(2)

#endregion

#region TimeSpan (Zeitdauer)

# Tick:
[TimeSpan]1

# oder Tage:
[timespan]5d

# Minute:
New-TimeSpan -Minutes 1 -Seconds 30

# Heute bis 31. Dez. 2020:
New-TimeSpan -End '2020-12-31'

# z.B.:
$cmdletDauer = Measure-Command -Expression { Get-ChildItem -Path C:\ -Directory -Recurse -ErrorAction Ignore }
$cmdletDauer | Get-Member
$cmdletDauer.TotalSeconds

#endregion

#region Array

# ? Eindimensionales Array zuweisen:

$eda = 1, 2, 3, 4, 5, 6
$eda = 1..1000
$eda[500]

$processes = Get-Process
$processes[1]
$processes[$processes.Count-1]
$processes | Select-Object -Last 1

# ? Zwei-/Mehrdimensionales Array zuweisen:

$mda = New-Object -TypeName ‘Int32[, ]’ -ArgumentList 2, 2
$mda = (11,12,13), (21,22,23), (31,32,33)
$mda[1][1]
$mda[$mda.Length-1]

# ? Gemischtes Array zuweisen:

$ga = 'Würzburg', 12, (Get-Process)
$ga.Length
$ga | Select-Object -Last 1
$ga[2][0]

# ! Array-Operatoren:

# ? Filtern (lt le ne eq ge gt):
10, 11, 12, 10 -eq 10

# ? Prüfen:
10, 11, 12, 10 -contains 10
10 -in 10, 11, 12, 10

# ? Leeres Array erstellen und befüllen:
$x = @()
$x += Get-Date -Format 'hhmmssff'
$x.Length
$x

# ! ACHTUNG !!! AN EIN ARRAY KÖNNEN KEINE NEUE ELEMENTE ANGEFÜGT WERDEN:

$varC = @()
Measure-Command { 1..2000 | ForEach-Object { $varC += "Objekt $_" } } | Select-Object TotalSeconds
$varC.Length
$varC[500]

# ! VS.

$varD = New-Object -TypeName 'System.Collections.ArrayList'
Measure-Command { 1..20000 | ForEach-Object { $varD.Add("Objekt $_") | Out-Null } } | Select-Object TotalSeconds
$varD.Count
$varD[500]

# ! Um die Lesegeschwindigkeit zu höhen, einfach eine ArrayList in ein Array umwandeln:
$varE = $varD.ToArray()

#endregion

#region ScriptBlock

# ! Ein Skriptblock ist eine Sammlung von Anweisungen oder Ausdrücken die als einzelne Einheit verwendet werden kann. Ein ScriptBlock hat die Aufgabe den Definitionszeitpunkt vom Ausführunszeitpunkt zu entkoppeln. Ein Skriptblock kann Argumente akzeptieren und Werte zurückgeben.

# READ Weiterführende und Nachschlage-Informationen:

Get-Help -Name 'about_script_blocks' -ShowWindow

# ! Anwendung:

# Definieren:
$sc = { Get-Process }
$sc | Get-Member

# Inhalt Script-Block anzeigen:
$sc

# Script-Block ausführen:
. $sc

# ! Beispiele:

Invoke-Command -ComputerName 'ServerX' -ScriptBlock { Get-Process }

Get-Process | Where-Object -FilterScript { $_.Company -like 'Microsoft*' -or $_.Company -like 'Oracle*' }

Get-ChildItem -Path 'C:\Temp' -File | ForEach-Object -Process {
    $_ | Add-Member -Name 'Besitzer' -Value ( $_ | Get-Acl | Select-Object -ExpandProperty 'Owner' ) -MemberType 'NoteProperty' -PassThru
}

#endregion

#region Hashtable

# ! Ein Hashtable ist ein besondere Collection (System.Collections.Hashtable), ähnlich eines Arrays deren Werte nicht über eine Indexnummer angesprochen werden sondern über einen eindeutigen Namen ('Hash')
# ! - Der Hashname kann frei gewählt werden MUSS aber im Hashtable eindeutig sein.
# ! - Zu jedem Hashnamen muss ein Value zugewiesen werden. Alle Objekte können als Value verwendet werden.
# ! - Beliebige viel Hashname-Value-Paare können in EINEM Hashtable enthalten sein.

# READ Weiterführende und Nachschlage-Informationen:

Get-Help -Name 'about_Hash_Tables' -ShowWindow

# ! Syntax:

@{ HashNameA = "Wert" ; HashNameB = "Wert" }
# oder:
@{
    # TIPP Zeilenumbrüche sind erlaubt, dann ist ein abschließendes ';' nicht nötig.
    HashNameA = "Wert"
    HashNameB = "Wert"
}

# ! FEHLER da der Hash-Name mehrdeutig ist:
@{ HashNameA = "Wert" ; HashNameA = "Wert" }

# ! Verwendungszweck 0)

$ht = @{ HashNameA = "Wert A" ; HashNameB = "Wert B" }
$ht['HashNameB']

# ! Verwendungszweck A): Um neue oder alt Eigenschaften an vorhanden Objekten anzuhängen bzw. zu überschreiben:

@{ Label = "" ; Expression = {} } # => @{ Label = "Label ist der Name der Eingenschaft"; Expression = { Expression enthält die Formel die den Werte der Eigenschaft zurück gibt } }

# ? z.B. Dateigröße in KB anzeigen bzw. mit KB weiter arbeiten (filtern, sortieren, etc.):
Get-ChildItem -Path 'c:\Windows' -File | Select-Object -Property 'Name', @{Label = 'LengthKB' ; Expression = {                  $_.Length / 1KB     } }
Get-ChildItem -Path 'c:\Windows' -File | Select-Object -Property 'Name', @{Label = 'LengthKB' ; Expression = { [int](           $_.Length / 1KB   ) } }
Get-ChildItem -Path 'c:\Windows' -File | Select-Object -Property 'Name', @{Label = 'LengthKB' ; Expression = { [Math]::Ceiling( $_.Length / 1KB   ) } }
Get-ChildItem -Path 'c:\Windows' -File | Select-Object -Property 'Name', @{Label = 'LengthKB' ; Expression = { [Math]::Round(   $_.Length / 1KB, 1) } }
Get-ChildItem -Path 'c:\Windows' -File | Select-Object -Property 'Name', @{Label = 'LengthKB' ; Expression = { '{0,8:0.0}' -f ( $_.Length / 1KB   ) } } # ! NACHTEIL: Output ist IMMER String

# ? z.B. Übersicht mit Dateiname, Dateigröße und Besitzer anzeigen/arbeiten:
Get-ChildItem -Path 'c:\Temp' -File
Get-ChildItem -Path 'c:\Temp' -File | Get-Acl
Get-ChildItem -Path 'c:\Temp' -File | Select-Object -Property 'Name', 'Length', @{ Label = 'Besitzer'; Expression = { $_ | Get-Acl | Select-Object -ExpandProperty 'Owner' } }

# TIPP: Dieses besondere Hashtable @{Label="";Expression={}} kann i.d.R. an jeden Parameter '-Property' benutzt werden, d.h. wie z.B. Select-Object kann durch andere Cmdlets ersetzt werden!

# ! Verwendungszweck B) Ein komplett neues Objekte mit eigenen definierten Eigenschaften erzeugen:

# ? z.B. Übersicht mit Dateiname, Dateigröße und Besitzer anzeigen/arbeiten:
Get-ChildItem -Path 'c:\Temp' -File | ForEach-Object -Process {
    [PSCustomObject]@{
        Dateiname    = $_.Name
        DateigrößeKB = [int]($_.Length / 1KB)
        Besitzer     = $_ | Get-Acl | Select-Object -ExpandProperty 'Owner'
    }
} | Get-Member

# ! ... oder ein Hashtable durch Benutzung erstellen:
$person = @{}
$person.Age = 23
$person.Name = 'Tobias'
$person.Status = 'Online'
[PsCustomObject]$person | Get-Member

# ! Verwendungszweck C) Splatting:

New-SelfSignedCertificate -Subject 'CN=_FirstName_LastName (PS Developer), E=v.nachname@abc.local' -HashAlgorithm 'SHA512' -KeyExportPolicy [Microsoft.CertificateServices.Commands.KeyExportPolicy]::ExportableEncrypted -CertStoreLocation 'Cert:\CurrentUser\My' -Type [Microsoft.CertificateServices.Commands.CertificateType]::CodeSigningCert -NotAfter (Get-Date).AddYears(5)
# ! vs.
$params = @{
    Subject           = 'CN=_FirstName_LastName (PS Developer), E=v.nachname@abc.local'
    HashAlgorithm     = 'SHA512'
    KeyExportPolicy   = [Microsoft.CertificateServices.Commands.KeyExportPolicy]::ExportableEncrypted
    CertStoreLocation = 'Cert:\CurrentUser\My'
    Type              = [Microsoft.CertificateServices.Commands.CertificateType]::CodeSigningCert
    NotAfter          = (Get-Date).AddYears(5)
}
New-SelfSignedCertificate @params # ! Aus $ muss @ werden

#endregion

#region StringData

# ! Mittels Data Section können Textzeichenfolgen oder andere schreibgeschützte Daten von der Skriptlogik isoliert werden.

# READ Weiterführende und Nachschlage-Informationen:

Get-Help -Name 'about_Data_Sections' -ShowWindow

# ? Beispiel:

$LocalizedData = Data {
    ConvertFrom-StringData @'
    CannotDetermineTestName = Cannot determine test name
    InvalidTestInfo = TestInfo must contain the path and the list of tests
    InspectingModules = Inspecting Modules
    DiagnosticSearch = Searching for Diagnostics in {0}
    InvokingTest = Invoking tests in {0}
'@

}
$LocalizedData.InvalidTestInfo
$LocalizedData.InvokingTest

#endregion

#region EMail

$emailAdresse = 'info@attilakrick.com'
$emailAdresse = [System.NET.Mail.MailAddress]'Attila Krick<info@attilakrick.com>' # TIPP: inkl. Syntax-Prüfung
$emailAdresse.Address
$emailAdresse.DisplayName

#endregion

#region XML

# ! Definition:

$exchange = [xml]@"
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
    <gesmes:subject>Reference rates</gesmes:subject>
    <gesmes:Sender>
        <gesmes:name>European Central Bank</gesmes:name>
    </gesmes:Sender>
    <Cube>
        <Cube time="2020-05-22">
            <Cube currency="USD" rate="1.0904"/>
            <Cube currency="JPY" rate="117.26"/>
            <Cube currency="BGN" rate="1.9558"/>
        </Cube>
        <Cube time="2020-05-21">
            <Cube currency="USD" rate="1.1"/>
            <Cube currency="JPY" rate="118.42"/>
            <Cube currency="BGN" rate="1.9558"/>
        </Cube>
    </Cube>
</gesmes:Envelope>
"@


# ! Verwendung:
$rate = $exchange.Envelope.Cube.Cube | Where-Object time -EQ '2020-05-22' | Select-Object -ExpandProperty 'Cube' | Where-Object currency -eq 'JPY' | Select-Object -ExpandProperty 'rate'
$rate * 100

# ! Beispiel für XPath und Schematas:

$xNamespaces = @{
    gesmes = 'http://www.gesmes.org/xml/2002-08-01'
    ecb    = 'http://www.ecb.int/vocabulary/2002-08-01/eurofxref'
}
$xDocument = [xml](Invoke-WebRequest -Uri 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml' | Select-Object -ExpandProperty 'Content')
$xInfo = $xDocument | Select-Xml -XPath "/gesmes:Envelope/ecb:Cube/ecb:Cube[@time='2020-05-22']" -Namespace $xNamespaces
$xInfo.Node.Cube

# ! XML-Daten auswerten:

[xml]$xDocument = Invoke-WebRequest -Uri 'http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml' | Select-Object -ExpandProperty 'Content'
[decimal]$rate = $xDocument.Envelope.Cube.Cube.Cube | Where-Object -Property 'currency' -EQ -Value 'AUD' | Select-Object -ExpandProperty 'rate'
$rate * 100

# ! In eine XML-Struktur schreiben:

[xml]$config = @"
<?xml version="1.0" encoding="utf-8" ?>
<Einstellungen>
    <Server Name="ADC01" IP="127.0.0.1" />
    <Server Name="ADC02" IP="127.0.0.1" />
</Einstellungen>
"@

($config.Einstellungen.Server  | Where-Object -Property 'Name' -Like -Value '*02').IP = '192.168.50.32'
$config.Save('C:\Temp\einstellung.xml')
[xml]$result = Get-Content 'C:\Temp\einstellung.xml'
$result.Einstellungen.Server

#endregion

#region Eigene Datentypen definieren

class Wechselkurs {
    [ValidatePattern('^[A-Z]{3,3}$')]
    [string]$Währung

    [ValidateRange(0, 10000)]
    [decimal]$Rate
}
$objekt = New-Object -TypeName Wechselkurs -Property @{Währung = 'USD'; Rate = 1.4567}
$objekt | Get-Member
$objekt.Währung = "AA"

#endregion

#region Werttypen vs. Referenztype

# ! Werttypen sind primitive Typen:

$a = 10
$b = 20
$a = $b
$b = 99
'$a = {0} ; $b = {1}' -f $a, $b
[Object]::ReferenceEquals($a, $b)
$b.GetType().IsPrimitive

# ! Referenztype:

$a = @(10)
$b = @(20)
$a = $b
$b[0] = 99
'$a = {0} ; $b = {1}' -f $a[0], $b[0]
[Object]::ReferenceEquals($a, $b)
$b.GetType().IsPrimitive

# ? Wert- oder Referenztype:

$a = [datetime]'2019-01-11' - [datetime]'2019-01-01'
$b = [datetime]'2019-01-21' - [datetime]'2019-01-01'
$a = $b
'$a = {0} ; $b = {1}' -f $a[0], $b[0]
[Object]::ReferenceEquals($a, $b)
$a.PSTypeNames
$b.GetType().IsPrimitive
$b.GetType().IsValueType

#endregion

# TODO QUIZ - https://attilakrick.com/schlagwort/powershell-objekte/