derhoeppi
Goto Top

Powershell Datenimport

Hallo,

ich bin gerade bei einem Gedankenspiel, welches ich auch schon kurz getestet habe. Ich habe eine CSV Datei, deren Inhalt ich importiere. Den Inhalt lasse ich durch eine Foreach Schleife laufen, so dass ich an die einzelnen Einträge herankomme. Die Einträge werden wiederum zu globalen Variablen gemacht. Nun habe ich das Problem, dass es in der CSV Datei ebenfalls Einträge gibt, die als Array angelegt werden sollen. Habt ihr einen Hinweis, wie ich das machen könnte. Der Delimeter für die CSV ist ";" und als Trenner für das Array möchte ich "," verwenden. Bisher bekomme ich über GetType immer einen String zurück.

Mein globales Ziel ist eine zentrale Datei, die mit sämtlichen Variablen und Arrays bestückt ist, die ich für alle meine Skripte nutzen kann. Dies soll der Vermeidung von Variablenüberschneidungen dienen.

Gruß
derhoeppi

Content-ID: 308474

Url: https://administrator.de/contentid/308474

Ausgedruckt am: 24.11.2024 um 02:11 Uhr

Kraemer
Kraemer 29.06.2016 um 11:31:07 Uhr
Goto Top
Moin,

ohne konkretes Beispiel wird es sehr schwer dir zu helfen.

Gruß Krämer
H41mSh1C0R
H41mSh1C0R 29.06.2016 um 11:33:53 Uhr
Goto Top
Hi derhoeppi,

das CSV in eine Hashtable einlesen.

In der Hashtable kommst du an jedes Element ran.

Das Feld das dann dein "Array" enthält kannst du mit normalen String Operationen (als Trenner das ",") zerlegen.

Gruß
colinardo
colinardo 29.06.2016 aktualisiert um 14:06:40 Uhr
Goto Top
Hallo derhoeppi,
für sowas habe ich hier schon mal einen Beitrag geschrieben, um Variablen, Einstellungen, Werte in einer XML-Datei zu speichern, zurückzuschreiben und wieder auszulesen:
Powershell: Werte aus einer XML-Datei auslesen und wieder darin speichern

Ansonsten wie @H41mSh1C0R schreibt, per Schleife deine Werte einer Hashtable zuweisen. Das ist wesentlich zielgerichteter als "extra Variablen" zu erzeugen. Auf die Werte in der Hashtable kannst du ja dann so zugreifen:
$hashtable['Einstellung1']
$hashtable['Einstellung2']
oder auch
$hashtable.'Einstellung1'

Hier ein simples Beispiel:
$csv = @"  
Variable;Typ;Wert
var1;string;Max Muster
var2;array;2001,2002,2003,2003
var3;int;100
"@ | convertfrom-CSV -Delimiter ";"  

# Hashtable hält unsere Daten
$data = @{}
# CSV durchlaufen und Werte der Hashtable zuweisen
foreach($line in $csv){
   switch($line.Typ){
        'int' {$data[$line.Variable] = [int]$line.Wert}  
        'array' {$data[$line.Variable] = [array]$line.Wert.Split(',')}  
        default {$data[$line.Variable] = [string]$line.Wert}
    }
}
$data
Ein Array kannst du für einen String ganz einfach erzeugen
"Bla,Blub" -split ','
oder
$array = $variable.Split(',')

Grüße Uwe
derhoeppi
derhoeppi 29.06.2016 um 14:56:48 Uhr
Goto Top
Hallo,

ich habe mich eben mal an den Beitrag von @H41mSh1C0R und @colinardo gemacht. Bei H41mSh1C0R habe ich es mit einem Split versucht - jedoch erfolglos. Der Typ der Variablen bleibt ein String und wird kein Array.

Mein Code sieht derzeit so aus:

$varibales_filename = "Variables.csv"  
$content = @{}
	$content = Import-Csv -Path $varibales_filename -Delimiter ";"  
	foreach ($value in $content)
	{
		if (Get-Variable -Scope global -Name $value.Value1 -ErrorAction SilentlyContinue)
		{
			Remove-Variable -Scope global -Name $value.Value1
		}
		New-Variable -Scope global -Name $value.Value1 -Value $value.Value2	
		$value.Value1 = $value.Value2.Split(',')  
	}

$content.gettype()
$Test1.Gettype()

Die Datei Variables.csv sieht so aus:
#TYPE System.Data.DataRowView
"Value1";"Value2"  
"Test1";"355, 366, 377, 388"  
"Test4";"3, 4, 5"  

Uwe sein Tipp mit der Hashtabelle habe ich auch aufgegriffen. Das finde ich insofern interessant, weil ich in meinen Skripts den Ansatz von Runspaces noch nicht verworfen habe. Mit der Hashtabelle hätte ich dann bereits einen Bereich den von allen Runspaces parallel darauf zugegriffen werden kann. Mein einziges Problem mit der Hashtabelle ist das direkte Adressieren der einzelnen Variablen. Ich müsste also nach dem Einlesen der CSV oder XML noch einmal eine Übernahme der Variablen durchführen.
In meinem jetzigen Codesnippsel habe ich das mit der foreach Schleife bereits getan, so dass ich direkt die Variable Test1 oder Test4 ansprechen kann. Wie würde ich das mit der Hashtabelle machen?
colinardo
colinardo 29.06.2016 aktualisiert um 15:04:46 Uhr
Goto Top
$content = @{}
$content = Import-Csv -Path $varibales_filename -Delimiter ";"
Falsch, du erstellst eine Hashtable und weist der Hashtable per Import-CSV ein Objekt zu, das geht nicht. Import-CSV erstellt ein Custom-Object keine Hashtable.

Mein einziges Problem mit der Hashtabelle ist das direkte Adressieren der einzelnen Variablen.
Wieso? Auf die Variablen einer Hashtable kannst du ganz bequem mit deren Namen ansprechen und auch Werte zuweisen:
Wie in meinem Beispiel oben:
$data['NameDerVariablen']
oder
$data.'Name Der Variablen'
usw. Ich sehe da überhaupt kein Problem!
colinardo
colinardo 29.06.2016 aktualisiert um 15:15:02 Uhr
Goto Top
Als Fortgeschrittener kann man es auch über XML-Object-Serialization und einer Klassendefinition machen face-smile, dann werden die Typen der Variablen automatisch wieder korrekt zugewiesen:
$pathSettings = 'A:\meine_app_settings.xml'  

# Klassendefinition
Add-Type -TypeDefinition @"  
    public class myAppSettings {
        public string var1;
        public int var2;
        public double var3;
        public string var4;
    }
"@ -EA Ignore  

# Objekt von Klasse erzeugen
$settings = new-object myAppSettings
$settings.var1 = "teststring"  
$settings.var2 = 333
$settings.var3 = 999383828
$settings.var4 = @("1","2","3","4")  

# XML-Serialization
$serializer = New-Object System.Xml.Serialization.XmlSerializer ([myAppSettings])
$writer = New-Object System.IO.StreamWriter($pathSettings)
$serializer.Serialize($writer,$settings)
$writer.Close()


# Settings-Objekt für die Demo zerstören
$settings = $null

# XML-Deserialization wieder aus XML-Datei laden
$serializer = New-Object System.Xml.Serialization.XmlSerializer([myAppSettings])
$settings = $serializer.Deserialize((New-Object System.IO.StreamReader($pathSettings)))

# geladene Daten wieder anzeigen
$settings

# Der Zugriff auf die einzelnen Eigenschaften(Variablen) erfolgt wie bei jedem anderen Custom-Objekt
$settings.var4
Einmal ein bißchen in der c# Doku gelesen und du hast auf einmal hunderte mehr Möglichkeiten face-smile
http://www.blackwasp.co.uk/xmlarrays.aspx
Powershell = .NET = C# = VB.Net (Lässt sich alles auch in Powershell abbilden)
derhoeppi
derhoeppi 29.06.2016 um 15:30:06 Uhr
Goto Top
Zitat von @colinardo:
Ich sehe da überhaupt kein Problem!

Hi Uwe,

das Problem in dem Fall bin ich selbst. Ich möchte ein Ziel erreichen, suche mir durch probieren einen Weg, der möglichst einfach sein sollte, damit ein Kollege es auch nachvollziehen kann. Mit dem PowerShell Studio von Sapien habe ich mir eine GUI mit einem GridView Element erstellt. Das soll mir den Inhalt der CSV-Datei / Hashtabelle anzeigen. Dort sollen die Variablen editierbar sein und das ganze soll wie in deinem XML Beitrag auch zurück in die CSV Datei geschrieben werden. Danach sollen die aktuellen Werte der CSV Datei eingelesen werden. Im Falle einer Hashtabelle müsste das sicherlich nicht erfolgen.

Am liebsten hätte eine Textdatei genommen in der einfach nur Var1=Max Mustermann und in der Zeile darunter Var2=1,2,3 steht. Das Skript sollte dann erkennen das es sich um einen String oder ein Array handelt. Im Fall von Var2 werde ich wohl wie in deinem Beispiel oben noch einen Wert zum Datentyp aufnehmen.
colinardo
colinardo 29.06.2016 aktualisiert um 16:28:48 Uhr
Goto Top
Du hast ja jetzt die Wahl face-smile Die Entscheidung können wir dir nicht abnehmen, nur mögliche Umsetzungen liefern.

@H41mSh1C0R hat das in dem Zusammenhang auch schon mal gefragt. Da habe ich Ihm hier auch zu einer XML und einer Datatable geraten, da es einfach über die GUI einem DataGridView zuweisbar und in die XML zurück zu schreiben ist: Powershell Datagridview und XML Datenhaltung
derhoeppi
derhoeppi 05.07.2016 um 14:47:14 Uhr
Goto Top
Hallo Uwe,

ich habe mich an die Version gewagt, die du h41msh1c0r im Post empfohlen hast.
Leider stehe ich nun an dem Punkt das ich meine Daten im DataGriedView nicht in die XML Datei schreiben kann. Das bedeutet ich kann die Daten der XML Datei einlesen, aber die Änderung wird nicht zurückgeschrieben. Das Einlesen und zurückschreiben der Daten erfolgt in unterschiedlichen Funktionen.


Das Einlesen funktioniert 1a
# Datatable erstellen
$dt = New-Object System.Data.DataTable
# Wichtig: Der Datatable einen Namen geben
$dt.TableName = "MeineDaten"  
# Spalten hinzufügen
$dt.Columns.Add('Name')  
$dt.Columns.Add('Alter')  
# Daten einlesen
$dt.ReadXml("C:\data.xml")  

Das Speichern jedoch nicht.
$dt.WriteXml("C:\data.xml")  

Beim exportieren mit der CSV sah mein Code so aus:
$datagridview.Rows | select -expand DataBoundItem | Export-csv -path "data.xml -delimeter ";"  
 
H41mSh1C0R
Lösung H41mSh1C0R 05.07.2016 aktualisiert um 14:57:27 Uhr
Goto Top
Was bekommst du für einen Fehler?

$table = $datagridviewResults.DataSource.Copy()
$table.WriteXml("$global:ProgramXMLPath\Library.xml", [system.data.xmlwritemode]::WriteSchema)  

Ich schreibe die Copy der Datatable weg.
colinardo
colinardo 05.07.2016 aktualisiert um 17:03:40 Uhr
Goto Top
Das Speichern jedoch nicht.
Aha, und du meinst die Info "funktioniert nicht" reicht jetzt aus oder was ??? Fehlermeldung?

Glaskugel rauskam ... Du hast wahrscheinlich dem DataGridView die Datatable nicht als Source Datenobjekt zugewiesen und schreibst deshalb die unveränderte Version zurück.

Aber alles nur Spekulation bei dem Informationsüberfluss face-sad

Hier ein simples Beispiel für den Im- und Export der Daten in/aus einer XML-Datei in ein DataGridView-Steuerelement:

$scriptpath = Split-Path $MyInvocation.MyCommand.Definition -Parent
$xmlpath = "$scriptpath\data.xml"  

function GenerateForm {

#region Import the Assemblies
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null  
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null  
#endregion

#region Generated Form Objects
$form1 = New-Object System.Windows.Forms.Form
$btnExport = New-Object System.Windows.Forms.Button
$btnImport = New-Object System.Windows.Forms.Button
$dgv = New-Object System.Windows.Forms.DataGridView
$InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
$dt = New-Object System.Data.DataTable

#endregion Generated Form Objects

#----------------------------------------------
# Event Script Blocks
#----------------------------------------------
$handler_form1_Load= 
{
    $dt.TableName = "Daten"  
    $dt.Columns.Add('Vorname')  
    $dt.Columns.Add('Nachname')  
    $dt.Rows.Add(@('Max','Muster'))  
    $dt.Rows.Add(@('Anna','Musterfrau'))  
    $dgv.DataSource = $dt
}

$handler_btnExport_Click= 
{
    $dt.WriteXml($xmlpath)
    [System.Windows.Forms.MessageBox]::Show("Daten wurden exportiert nach '$xmlpath' !")  
}

$handler_btnImport_Click= 
{
    if ((Test-Path $xmlpath)){
        $dt.Clear()
        $dt.ReadXml($xmlpath)
        [System.Windows.Forms.MessageBox]::Show("Daten wurden importiert von '$xmlpath'!")  
    }else{
        [System.Windows.Forms.MessageBox]::Show("Führen sie erst einen Export durch!")  
    }

}

$OnLoadForm_StateCorrection=
{#Correct the initial state of the form to prevent the .Net maximized form issue
	$form1.WindowState = $InitialFormWindowState
}

#----------------------------------------------
#region Generated Form Code
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 183
$System_Drawing_Size.Width = 340
$form1.ClientSize = $System_Drawing_Size
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.Name = "form1"  
$form1.Text = "DataGridView Load&Export"  
$form1.add_Load($handler_form1_Load)

$btnExport.Anchor = 10

$btnExport.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 253
$System_Drawing_Point.Y = 157
$btnExport.Location = $System_Drawing_Point
$btnExport.Name = "btnExport"  
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 75
$btnExport.Size = $System_Drawing_Size
$btnExport.TabIndex = 2
$btnExport.Text = "Exportieren"  
$btnExport.UseVisualStyleBackColor = $True
$btnExport.add_Click($handler_btnExport_Click)

$form1.Controls.Add($btnExport)

$btnImport.Anchor = 10

$btnImport.DataBindings.DefaultDataSourceUpdateMode = 0

$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 172
$System_Drawing_Point.Y = 157
$btnImport.Location = $System_Drawing_Point
$btnImport.Name = "btnImport"  
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 23
$System_Drawing_Size.Width = 75
$btnImport.Size = $System_Drawing_Size
$btnImport.TabIndex = 1
$btnImport.Text = "Importieren"  
$btnImport.UseVisualStyleBackColor = $True
$btnImport.add_Click($handler_btnImport_Click)

$form1.Controls.Add($btnImport)

$dgv.Anchor = 15
$dgv.DataBindings.DefaultDataSourceUpdateMode = 0
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 12
$System_Drawing_Point.Y = 12
$dgv.Location = $System_Drawing_Point
$dgv.Name = "dgv"  
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 139
$System_Drawing_Size.Width = 316
$dgv.Size = $System_Drawing_Size
$dgv.TabIndex = 0

$form1.Controls.Add($dgv)

#endregion Generated Form Code

#Save the initial state of the form
$InitialFormWindowState = $form1.WindowState
#Init the OnLoad event to correct the initial state of the form
$form1.add_Load($OnLoadForm_StateCorrection)
#Show the Form
$form1.ShowDialog()| Out-Null

} #End Function

GenerateForm
derhoeppi
derhoeppi 05.07.2016 um 19:40:49 Uhr
Goto Top
Hi,

mit der Zuweisung der DataSource Copy funktioniert es. Für mein Verständnis ist dabei jedoch eine Frage aufgekommen. Warum muss ich eine Copy anlegen. Beim Einlesen der XML Datei habe ich die Daten in eine DataTable $dt geschrieben. Diese wiederum als DataSource dem DataGridView zugewiesen. Ich bin also davon ausgegangen, dass wenn ich eine Änderung im DataGridView durchführe - diese dann ebenfalls in der DataTable $dt geändert wird. Wenn ich aber jetzt eine Kopie der DataSource anlege ist dem also nicht so?
colinardo
Lösung colinardo 05.07.2016 aktualisiert um 20:06:44 Uhr
Goto Top
Warum muss ich eine Copy anlegen.
Musst du definitiv nicht. Siehe mein funktionsfähiges Beispiel oben.
Ich bin also davon ausgegangen, dass wenn ich eine Änderung im DataGridView durchführe - diese dann ebenfalls in der DataTable $dt geändert wird.
Das ist auch absolut richtig. Bei meiner Variante oben benötigst du keine Kopie, weil das DataGridView ja direkt mit der DataSource verbunden ist! Probiere mein Beispiel oben aus dann wirst du sehen das du Änderungen vornehmen kannst und diese direkt per WriteXML wegschreiben kannst ohne extra eine Kopie dieser zu erstellen. Ging schon immer einwandfrei.

Ich vermute du machst in deinem Code einfach eine fehlerhafte Variablendeklaration und änderst die Quelle aus einem anderen Scope, deswegen geht es bei dir vermutlich nur per Kopie. Wenn man die Variablen im richtigen Scope deklariert und verwendet geht das auch ohne Kopie problemlos.

about_Scopes
derhoeppi
derhoeppi 06.07.2016 um 11:26:12 Uhr
Goto Top
Hallo Uwe,
du hast natürlich recht. Da das PowerShell Studio noch neu für mich ist, muss ich noch sehen wo ich etwas definiere. Ich habe die Deklaration der DataTable nun aber global vorgenommen, so dass es wie gewünscht funktioniert.

Nun sitze ich an deinem Code den du zum Import der CSV gepostet hast.
$data = @{}
# CSV durchlaufen und Werte der Hashtable zuweisen
foreach($line in $csv){
   switch($line.Typ){
        'int' {$data[$line.Variable] = [int]$line.Wert}  
        'array' {$data[$line.Variable] = [array]$line.Wert.Split(',')}  
        default {$data[$line.Variable] = [string]$line.Wert}
    }
}

Ich möchte also die Daten der DataTable in eine Hashtable schreiben und den einzelnen Variablen ihren Datentyp zuweisen.
colinardo
colinardo 06.07.2016 aktualisiert um 12:00:07 Uhr
Goto Top
Iteriere einfach über alle Rows deiner Datatable mit einer Schleife und weise deiner Hashtable die Werte wie oben zu, feddich ...

Beispiel:
$data = @{}
$dt = New-Object System.Data.DataTable
$dt.TableName = "Daten"  
[void]$dt.Columns.Add('Variable')  
[void]$dt.Columns.Add('Typ')  
[void]$dt.Columns.Add('Wert')  
[void]$dt.Rows.Add(@('var01','string','Das ist ein String'))  
[void]$dt.Rows.Add(@('var02','array','400,500,600'))  

foreach ($r in $dt.Rows){
    switch($r.Typ){
        'int' {$data[$r.Variable] = [int]$r.Wert}  
        'array' {$data[$r.Variable] = [array]$r.Wert.Split(',')}  
        default {$data[$r.Variable] = [string]$r.Wert}
    }
}
$data
derhoeppi
derhoeppi 06.07.2016 um 19:51:23 Uhr
Goto Top
Hallo Uwe,

danke das du das letzte Code-Beispiel noch erstellt hast. In der Zwischenzeit habe ich meinen Fehler beim Iterieren gefunden. Nochmal herzlichen Dank. Nun kann ich meine GUI weiter gestalten und meine bisherigen Skripte einbauen.