W pierwszej części poznaliście podstawy tworzenia skryptów wykorzystujących interfejs okienkowy. w tym poście wykorzystamy tą wiedzę oraz nowe elementy do stworzenia w pełni funkcjonalnej aplikacji. A będzie to aplikacja do zarządzania snapshotami w Active Directory. Sposób tworzenia snapshotów był opisany w tym wpisie. Zacznij zatem od przygotowania pustej formatki.
Add-Type -AssemblyName System.Windows.Forms
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "AD Snapshot Manager"
$Form.Size = New-Object System.Drawing.Size(640,480)
$Form.ShowDialog()
Żeby lista snapshotów ładnie wyglądała dołóżmy obiekt typu Group Box
#GroupBox
$groupbox = New-Object System.Windows.Forms.GroupBoxd
$groupbox.Location = New-Object System.Drawing.Size(10,10)
$groupbox.Size = New-Object System.Drawing.Size(600,200)
$groupbox.Text = "List Snapshots:"
$form.Controls.Add($groupbox)
Następnie z pomocą Grid View zróbmy widok listy snapshotów. Dodałem do obiektu DataGridView parę dodatkowych atrybutów, które uniemożliwią jego edycję oraz usuną zbędne elementy. Na koniec określiłem liczbę, nazwałem kolumny oraz uruchomiłem automatyczne dopasowanie szerokości do wartości komórek.
#GridView list snapshots
$gridview = New-Object System.Windows.Forms.DataGridView
$gridview.Location = New-Object System.Drawing.Size(10,20)
$gridview.Size = New-Object System.Drawing.Size(580,170)
$gridview.ReadOnly = $true
$gridview.AllowUserToAddRows = $false
$gridview.AllowUserToDeleteRows = $false
$gridview.RowHeadersVisible = $false
$gridview.SelectionMode = [System.Windows.Forms.DataGridViewSelectionMode]::FullRowSelect
$gridview.ColumnCount = 4
$gridview.ColumnHeadersVisible = $true
$gridview.Columns[0].Name = "Snapshot time"
$gridview.Columns[0].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::DisplayedCells
$gridview.Columns[1].Name = "GUID"
$gridview.Columns[1].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::DisplayedCells
$gridview.Columns[2].Name = "Is Mounted"
$gridview.Columns[2].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::DisplayedCells
$gridview.Columns[3].Name = "Path"
$gridview.Columns[3].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::Fill
$groupbox.Controls.Add($gridview)
Na chwilę obecną całość prezentuje się następująco:
Dodajmy zatem kawałek kodu, który będzie wypełniał powyższą tabelę. Z uwagi, że polecenie ntdsutil jest aplikacją wiersza poleceń, a nie powershella, to prezentowane wyniki są tekstem, a nie obiektem. W tym przykładzie użyłem sporo funkcji operujących na tekście, tak aby przekształcić wyłuskać to co potrzebuję z ciągów tekstowych.
$gridview.Columns[3].Name = "Path"
$gridview.Columns[3].AutoSizeMode = [System.Windows.Forms.DataGridViewAutoSizeColumnMode]::DisplayedCells
$list = Invoke-Command -ScriptBlock { cmd.exe '/c ntdsutil snapshot "list all" quit quit' }
$list_mounted = Invoke-Command -ScriptBlock { cmd.exe '/c ntdsutil snapshot "list mounted" quit quit' }
$list_output = @()
ForEach($line in $list)
{
$list_output += $line | Select-String -SimpleMatch "/"
}
ForEach($snap in $list_output)
{
#$snap_no = $snap.ToString().SubString(1,$snap.ToString().indexOf(":")-1)
$snap_date = [datetime]::ParseExact(($snap.ToString().Split(" ") | Select -Index 2), 'yyyy/MM/dd:HH:mm', $null)
$snap_guid = $snap.ToString().SubString($snap.ToString().indexOf("{"),$snap.ToString().indexOf("}")-$snap.ToString().indexOf("{")+1)
if($list_mounted | Select-String -SimpleMatch $snap_guid )
{
$mounted = $true
$search = $list_mounted | Select-String -SimpleMatch $snap_guid -Context 1
$snap_path = $search.Context.PostContext | Out-String
$snap_path = ($snap_path).SubString(($snap_path).IndexOf("} ")+2,($snap_path).IndexOf("$\")-($snap_path).IndexOf("} "))
}
else
{
$mounted = $false
}
$gridview.Rows.Add($snap_date.ToString('dd.MM.yyyy HH:mm'),$snap_guid,$mounted,$snap_path)
Clear-Variable snap_path
}
$groupbox.Controls.Add($gridview)
I tak oto prezentuje się w chwili obecnej aplikacja, gdzie do elementu Grid View, są ładowane aktywne snapshoty Active Directory.
Samo wyświetlanie w wygodnej, tabelarycznej formie to już niezłe osiągnięcie. Idźmy zatem dalej, dodajmy jeszcze kilka przycisków, aby powiększyć funkcjonalność. Na początek dodam 5 przycisków odpowiadających za: utworzenie snapshota, zamontowanie, odmontowanie, odświeżenie formatki oraz wyłączenie aplikacji. Do roboty!
$Createbutton = New-Object System.Windows.Forms.Button
$Createbutton.Location = New-Object System.Drawing.Size(35,220)
$Createbutton.Size = New-Object System.Drawing.Size(100,25)
$Createbutton.Text = "Create"
$Form.Controls.Add($Createbutton)
$mountbutton = New-Object System.Windows.Forms.Button
$mountbutton.Location = New-Object System.Drawing.Size(155,220)
$mountbutton.Size = New-Object System.Drawing.Size(100,25)
$mountbutton.Text = "Mount"
$Form.Controls.Add($mountbutton)
$unmountbutton = New-Object System.Windows.Forms.Button
$unmountbutton.Location = New-Object System.Drawing.Size(265,220)
$unmountbutton.Size = New-Object System.Drawing.Size(100,25)
$unmountbutton.Text = "Unmount"
$Form.Controls.Add($unmountbutton)
$refreshbutton = New-Object System.Windows.Forms.Button
$refreshbutton.Location = New-Object System.Drawing.Size(375,220)
$refreshbutton.Size = New-Object System.Drawing.Size(100,25)
$refreshbutton.Text = "Refresh"
$Form.Controls.Add($refreshbutton)
$exitbutton = New-Object System.Windows.Forms.Button
$exitbutton.Location = New-Object System.Drawing.Size(485,220)
$exitbutton.Size = New-Object System.Drawing.Size(100,25)
$exitbutton.Text = "Exit"
$Form.Controls.Add($exitbutton)
Przy okazji zmniejszyłem trochę rozmiar formatki, dostosowując ją do użytych elementów. Całość prezentuje się następująco.
Czas, żeby dodać akcję do poszczególnych przycisków.
$Createbutton = New-Object System.Windows.Forms.Button
$Createbutton.Location = New-Object System.Drawing.Size(35,220)
$Createbutton.Size = New-Object System.Drawing.Size(100,25)
$Createbutton.Text = "Create"
$Createbutton.Add_Click({
$create = Invoke-Command -ScriptBlock { cmd.exe '/c ntdsutil snapshot "Activate Instance NTDS" create quit quit' }
[System.Windows.Forms.MessageBox]::Show(($create | Select-String -SimpleMatch "{"))})
$Form.Controls.Add($Createbutton)
Pisząc akcję do pierwszego przycisku, pomyślałem, że w sumie dobrym rozwiązaniem było by dodanie jeszcze przycisku usunięcia snapshota. Po dodaniu, żeby estetycznie to wyglądało rozszerzyłem nieco formatkę. Wygląda teraz następująco.
Kod przycisku usunięcia snapshota
$removebutton = New-Object System.Windows.Forms.Button
$removebutton.Location = New-Object System.Drawing.Size(120,220)
$removebutton.Size = New-Object System.Drawing.Size(100,25)
$removebutton.Text = "Remove"
$removebutton.Add_Click({
$command = '/c ntdsutil snapshot "delete ' + $gridview.CurrentRow.Cells["GUID"].Value + '" quit quit"'
$remove = Invoke-Command -ScriptBlock { cmd.exe $command }
[System.Windows.Forms.MessageBox]::Show(($remove | Select-String -SimpleMatch "{")[1])
})
$Form.Controls.Add($removebutton)
Kod dla kolejnego przycisku, czyli mount
$mountbutton = New-Object System.Windows.Forms.Button
$mountbutton.Location = New-Object System.Drawing.Size(230,220)
$mountbutton.Size = New-Object System.Drawing.Size(100,25)
$mountbutton.Text = "Mount"
$mountbutton.Add_Click({
if($gridview.CurrentRow.Cells["Is Mounted"].Value -like "False")
{
$command = '/c ntdsutil snapshot "mount ' + $gridview.CurrentRow.Cells["GUID"].Value + '" quit quit"'
$mount = Invoke-Command -ScriptBlock { cmd.exe $command}
[System.Windows.Forms.MessageBox]::Show(($mount | Select-String -SimpleMatch "{")[1])
}
else
{
[System.Windows.Forms.MessageBox]::Show("Snapshot already mounted")
}
})
$Form.Controls.Add($mountbutton)
Przycisk odmontowania snapshota, ma kod bardzo zbliżony do montowania, tylko odwrotny warunek
$unmountbutton = New-Object System.Windows.Forms.Button
$unmountbutton.Location = New-Object System.Drawing.Size(340,220)
$unmountbutton.Size = New-Object System.Drawing.Size(100,25)
$unmountbutton.Text = "Unmount"
$unmountbutton.Add_Click({
if($gridview.CurrentRow.Cells["Is Mounted"].Value -like "True")
{
$command = '/c ntdsutil snapshot "unmount ' + $gridview.CurrentRow.Cells["GUID"].Value + '" quit quit"'
$unmount = Invoke-Command -ScriptBlock { cmd.exe $command}
[System.Windows.Forms.MessageBox]::Show(($unmount | Select-String -SimpleMatch "{")[1])
}
else
{
[System.Windows.Forms.MessageBox]::Show("Snapshot didn't mounted")
}})
$Form.Controls.Add($unmountbutton)
Przyszedł czas na przycisk refresh, niby prosta sprawa, odświeżenie formatki. Jednak w powershellu to jest taka oczywista kwestia. Ja to rozwiązałem poprzez zamknięcie i ponowne wygenerowanie formatki, czyli:
function Form
{
<kod formatki>
...
$refreshbutton.Add_Click({
$form.Dispose()
$Form.Close()
Form
})
...
$Form.ShowDialog()
}
Form
Pełny kod wygląda u mnie następująco:
$refreshbutton = New-Object System.Windows.Forms.Button
$refreshbutton.Location = New-Object System.Drawing.Size(450,220)
$refreshbutton.Size = New-Object System.Drawing.Size(100,25)
$refreshbutton.Text = "Refresh"
$refreshbutton.Add_Click({
$form.Dispose()
$Form.Close()
Form
})
$Form.Controls.Add($refreshbutton)
Z przyciskiem wyjścia z aplikacji sprawa jest najprostsza, bo to tylko zamknięcie formatki
$exitbutton = New-Object System.Windows.Forms.Button
$exitbutton.Location = New-Object System.Drawing.Size(560,220)
$exitbutton.Size = New-Object System.Drawing.Size(100,25)
$exitbutton.Text = "Exit"
$exitbutton.Add_Click({$Form.Close()})
$Form.Controls.Add($exitbutton)
I na tym możemy zakończyć naszą aplikację, posiada wszystkie podstawowe funkcjonalności i może ułatwić codzienną pracę administratora Active Directory. Pewnie aplikacja ma jeszcze sporo błędów i braku odporności na wyjątki, które trzeba dopisać, ale to już nie w tym wątku. Tutaj ideą było napisać w powershellu aplikację, wykorzystującą interfejs okienkowy. Jak zawsze cały kod możecie znaleźć na githubie. Jeśli chcecie kolejne części, dajcie znać w komentarzu.
3 komentarze do “Okienkowe skrypty, czyli GUI w PowerShellu cz 2”