Visual Studio给Powershell 程序创建可视化界面

简介:

豆子经常写些Powershell的小脚本用来管理Windows服务器。有的时候我希望能够把这些小工具分享给同事,不过有些同事对于Powershell一窍不通,对于脚本控制台界面更是深恶痛绝,如果有些傻瓜化的图像界面便于操作就好了。豆子在网上做了些小研究,写了个简单的测试界面,下面分享一下心得。


网上有一些第三方的付费工具可以直接进行GUI的开发,穷人舍不得花钱,豆子用的是微软的Visual Studio 2015的社区版。这个是版本是免费下载的,下载安装很费时间,大概花了2个小时。这个是下载链接  https://www.visualstudio.com/en-us/downloads/download-visual-studio-vs.aspx


简单的说,我需要在visual studio 上设计图形界面,生成对应的XAMl文件。XAML是基于XML的语言,一般用来定义Windows的图像界面。如果能想办法在Powershell里面识别出XAML文件的元素,Powershell就可以直接生成对应的图像界面。每一个元素对象都有自己的属性和方法,因此我可以直接对他们进行操作。


这篇博文微软官方提供了一个很好的脚本对XAML文件进行翻译,并进行了详细解释

http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/01/i-39-ve-got-a-powershell-secret-adding-a-gui-to-scripts.aspx


下面这个博文提供了一个模板,他基于上一篇的脚本进行了加工,操作变得更简单。我们需要的就是生成XAMI文件,然后拷贝到其对应的字符串里,对于这个字符串的解析就不用自己操心了。

http://foxdeploy.com/2015/04/10/part-i-creating-powershell-guis-in-minutes-using-visual-studio-a-new-hope/


下面来个实际的例子


打开visual studio,然后新建一个项目,选择WPF

wKioL1aoT2LAi7S8AACxe-98Q0k802.png


然后根据自己的需求,打开工具栏,拖曳成自己想要的界面。


比如说,下面是豆子的练习之作,我希望查询近期AD账户被锁的记录,同时可以一键解锁所有被锁账户。下面用到的几个工具是label, button, image 和listViewer,如果用过任何开发工具的人应该都很熟悉这些。


wKiom1aoTyiBJOptAADzvcg3wJU354.png


注意当我在主界面上拿鼠标拖曳这些图像控件的时候,他会自动生成对应的XAML文件

wKiom1aoTyuAdawNAACBxn4rRec772.png


复制这些内容,然后粘贴到下面的Powershell 模板文件的@@符号之间里面。 如下所示


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#ERASE ALL THIS AND PUT XAML BELOW between the @" "@ 
$inputXML  @"
<Window x:Class="WpfApplication3.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApplication3"
         mc:Ignorable="d"
         Title="UnlockTool" Height="350" Width="525" Background="#FFCDDFEC">
     <Grid>
         <Label x:Name="label" Content="Click the Button to query LockOut history" HorizontalAlignment="Left" Margin="18,10,0,0" VerticalAlignment="Top" Width="292"/>
         <Button x:Name="button" Content="Query" HorizontalAlignment="Left" Margin="27,56,0,0" VerticalAlignment="Top" Width="75"/>
         <Button x:Name="button1" Content="Unlock" HorizontalAlignment="Left" Margin="27,260,0,0" VerticalAlignment="Top" Width="75"/>
         <ListView x:Name="listView" HorizontalAlignment="Left" Height="140" Margin="27,99,0,0" VerticalAlignment="Top" Width="454">
             <ListView.View>
                 <GridView>
                     <GridViewColumn Header="UserName" DisplayMemberBinding ="{Binding 'username'}" Width="160"/>
                     <GridViewColumn Header="LockOut Computer" DisplayMemberBinding ="{Binding 'computer'}" Width="160"/>
                     <GridViewColumn Header="LockOut Time" DisplayMemberBinding ="{Binding 'time'}" Width="160"/>
                 </GridView>
             </ListView.View>
         </ListView>
         <Image x:Name="image" HorizontalAlignment="Left" Height="75" Margin="372,10,0,0" VerticalAlignment="Top" Width="95" Source="c:\temp\unlock.png"/>
     </Grid>
</Window>
"@        
$inputXML  $inputXML  -replace  'mc:Ignorable="d"' , ''  -replace  "x:N" , 'N'   -replace  '^<Win.*' '<Window'
[void][System.Reflection.Assembly] ::LoadWithPartialName( 'presentationframework' )
[xml] $XAML  $inputXML
#Read XAML
     $reader =( New-Object  System.Xml.XmlNodeReader  $xaml
   try{ $Form = [Windows.Markup.XamlReader] ::Load(  $reader  )}
catch{ Write-Host  "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed." }
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$xaml .SelectNodes( "//*[@Name]" ) | %{ Set-Variable  -Name  "WPF$($_.Name)"  -Value  $Form .FindName( $_ .Name)}
Function  Get-FormVariables {
if  ( $global:ReadmeDisplay  -ne  $true ){ Write-host  "If you need to reference this display again, run Get-FormVariables"  -ForegroundColor Yellow; $global:ReadmeDisplay = $true }
write-host  "Found the following interactable elements from our form"  -ForegroundColor Cyan
get-variable  WPF*
}
Get-FormVariables
#===========================================================================
# Actually make the objects work
#===========================================================================
 
#===========================================================================
# Shows the form
#===========================================================================
write-host  "To show the form, run the following"  -ForegroundColor Cyan
function  Show-Form {
$Form .ShowDialog() |  out-null
}
Show-Form


如果直接执行上面的代码的话,Powershell ISE 会生成对应的图像界面,并且在控制台输出当前的元素对象,这些解析出来的元素对象是可以直接在powershell里面操作的。



wKioL1aoUdLwUpIXAAD5l52pvRY029.png


界面有了,接下来我需要添加真正的操作代码。


首先自己写一个方法从PDC上获取4740日志,这个需要事先在GPO上打开审计功能。对其XML的内容格式进行分析之后,进行输出,获取自己需要的内容。这里我需要知道用户名,从哪里锁住的,以及锁住的时间。


关于Windows 日志处理,可以参考我的另外一篇博文

http://beanxyz.blog.51cto.com/5570417/1695288


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function  get-lockout {
$eventcritea  = @{logname= 'security' ;id=4740}
$Events  = get-winevent  -ComputerName ( Get-ADDomain ).pdcemulator -FilterHashtable  $eventcritea 
#$Events = Get-WinEvent -ComputerName syddc01 -Filterxml $xmlfilter        
             
# Parse out the event message data            
ForEach  ( $Event  in  $Events ) {    
       
     # Convert the event to XML            
     $eventXML  [xml] $Event .ToXml()    
           
     # Iterate through each one of the XML message properties            
     For  ( $i =0;  $i  -lt  $eventXML .Event.EventData.Data.Count;  $i ++) { 
      
             
         # Append these as object properties            
         Add-Member  -InputObject  $Event  -MemberType NoteProperty -Force -Name   $eventXML .Event.EventData.Data[ $i ].name -Value  $eventXML .Event.EventData.Data[ $i ]. '#text'            
     }            
}            
$events  | select  @{n= 'UserName' ;e={ $_ .targetusername}},@{n= 'Computer' ;e={ $_ .targetdomainname}},@{n= 'time' ;e={ $_ .timecreated}}
}



接下来写事件的函数,比如单击第一个按钮,自动调用get-lockout方法,通过管道进行输出;第二个按钮直接解锁所有的用户。


1
2
3
4
5
6
7
8
9
$WPFbutton .add_click({
get-lockout  | %{ $WPFlistView .AddChild( $_ )}
}
   )
     
$WPFbutton1 .add_click(
{
Search-ADAccount -LockedOut |  ForEach-Object  {Unlock-ADAccount -Identity  $_ .distinguishedname -PassThru }
})


注意第一个按钮的事件,我这里试图一行一行的在GridView里面添加数据,默认情况下他并不知道哪里数据需要添加,而是会把所有的数据都加在每一格子的数据里面,因此我们需要在前面的Grid定义里面手动绑定一下,注意DisplayMemberBinding 里面绑定的是我自定义的输出字段的名字


1
2
3
4
5
6
7
<ListView.View>
                 <GridView>
                     <GridViewColumn Header= "UserName"  DisplayMemberBinding = "{Binding 'username'}"  Width= "160" />
                     <GridViewColumn Header= "LockOut Computer"  DisplayMemberBinding = "{Binding 'computer'}"  Width= "160" />
                     <GridViewColumn Header= "LockOut Time"  DisplayMemberBinding = "{Binding 'time'}"  Width= "160" />
                 </GridView>
             </ListView.View>


运行结果如下所示

wKioL1aoU_mATOfzAACQu4lBdkI988.png


下面是完整的脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#ERASE ALL THIS AND PUT XAML BELOW between the @" "@ 
$inputXML  @"
<Window x:Class="WpfApplication3.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApplication3"
         mc:Ignorable="d"
         Title="UnlockTool" Height="350" Width="525" Background="#FFCDDFEC">
     <Grid>
         <Label x:Name="label" Content="Click the Button to query LockOut history" HorizontalAlignment="Left" Margin="18,10,0,0" VerticalAlignment="Top" Width="292"/>
         <Button x:Name="button" Content="Query" HorizontalAlignment="Left" Margin="27,56,0,0" VerticalAlignment="Top" Width="75"/>
         <Button x:Name="button1" Content="Unlock" HorizontalAlignment="Left" Margin="27,260,0,0" VerticalAlignment="Top" Width="75"/>
         <ListView x:Name="listView" HorizontalAlignment="Left" Height="140" Margin="27,99,0,0" VerticalAlignment="Top" Width="454">
             <ListView.View>
                 <GridView>
                     <GridViewColumn Header="UserName" DisplayMemberBinding ="{Binding 'username'}" Width="160"/>
                     <GridViewColumn Header="LockOut Computer" DisplayMemberBinding ="{Binding 'computer'}" Width="160"/>
                     <GridViewColumn Header="LockOut Time" DisplayMemberBinding ="{Binding 'time'}" Width="160"/>
                 </GridView>
             </ListView.View>
         </ListView>
         <Image x:Name="image" HorizontalAlignment="Left" Height="75" Margin="372,10,0,0" VerticalAlignment="Top" Width="95" Source="c:\temp\unlock.png"/>
     </Grid>
</Window>
"@        
$inputXML  $inputXML  -replace  'mc:Ignorable="d"' , ''  -replace  "x:N" , 'N'   -replace  '^<Win.*' '<Window'
[void][System.Reflection.Assembly] ::LoadWithPartialName( 'presentationframework' )
[xml] $XAML  $inputXML
#Read XAML
     $reader =( New-Object  System.Xml.XmlNodeReader  $xaml
   try{ $Form = [Windows.Markup.XamlReader] ::Load(  $reader  )}
catch{ Write-Host  "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed." }
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$xaml .SelectNodes( "//*[@Name]" ) | %{ Set-Variable  -Name  "WPF$($_.Name)"  -Value  $Form .FindName( $_ .Name)}
Function  Get-FormVariables {
if  ( $global:ReadmeDisplay  -ne  $true ){ Write-host  "If you need to reference this display again, run Get-FormVariables"  -ForegroundColor Yellow; $global:ReadmeDisplay = $true }
write-host  "Found the following interactable elements from our form"  -ForegroundColor Cyan
get-variable  WPF*
}
Get-FormVariables
#===========================================================================
# Actually make the objects work
#===========================================================================
function  get-lockout {
$eventcritea  = @{logname= 'security' ;id=4740}
$Events  = get-winevent  -ComputerName ( Get-ADDomain ).pdcemulator -FilterHashtable  $eventcritea 
#$Events = Get-WinEvent -ComputerName syddc01 -Filterxml $xmlfilter        
             
# Parse out the event message data            
ForEach  ( $Event  in  $Events ) {    
       
     # Convert the event to XML            
     $eventXML  [xml] $Event .ToXml()    
           
     # Iterate through each one of the XML message properties            
     For  ( $i =0;  $i  -lt  $eventXML .Event.EventData.Data.Count;  $i ++) { 
      
             
         # Append these as object properties            
         Add-Member  -InputObject  $Event  -MemberType NoteProperty -Force -Name   $eventXML .Event.EventData.Data[ $i ].name -Value  $eventXML .Event.EventData.Data[ $i ]. '#text'            
     }            
}            
$events  | select  @{n= 'UserName' ;e={ $_ .targetusername}},@{n= 'Computer' ;e={ $_ .targetdomainname}},@{n= 'time' ;e={ $_ .timecreated}}
}
$WPFbutton .add_click({
get-lockout  | %{ $WPFlistView .AddChild( $_ )}
}
   )
     
$WPFbutton1 .add_click(
{
Search-ADAccount -LockedOut |  ForEach-Object  {Unlock-ADAccount -Identity  $_ .distinguishedname -PassThru }
})
#Reference Sample entry of how to add data to a field
     #$vmpicklistView.items.Add([pscustomobject]@{'VMName'=($_).Name;Status=$_.Status;Other="Yes"})
     #$WPFtextBox.Text = $env:COMPUTERNAME
     #$WPFbutton.Add_Click({$WPFlistView.Items.Clear();start-sleep -Milliseconds 840;Get-DiskInfo -computername $WPFtextBox.Text | % {$WPFlistView.AddChild($_)}  })
#===========================================================================
# Shows the form
#===========================================================================
write-host  "To show the form, run the following"  -ForegroundColor Cyan
function  Show-Form {
$Form .ShowDialog() |  out-null
}
Show-Form


最后,豆子不希望别个看见源代码的话,可以考虑转换成EXE文件。从本质上说,powershell没法编译成可执行文件,但是可以“包装”在一个exe文件里面。一个免费的工具是PowerGUI,可以从Dell的网站上下载。

wKiom1aoVeLTTVleAACTuQ8Q880933.png

每次执行EXE其实仍然会执行一个解压的操作,然后自动加载PowerShell,这样的等待时间会稍长,因此豆子更宁愿直接运行ps1文件。










本文转自 beanxyz 51CTO博客,原文链接:http://blog.51cto.com/beanxyz/1739164,如需转载请自行联系原作者

目录
相关文章
|
7天前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
4月前
在visual studio中调试程序 管理员权限添加
在visual studio中调试程序 管理员权限添加
98 0
使用Visual studio 2013 创建C语言Helloworld程序
使用Visual studio 2013 创建C语言Helloworld程序
使用Visual studio 2013 创建C语言Helloworld程序
Visual Studio 2019 设置程序结束控制台不关闭
修改设置使控制台应用运行结束,控制台不自动退出。
379 0
Visual Studio 2019 设置程序结束控制台不关闭
|
IDE 开发工具
Visual Studio下程序开发: 编译QT程序Debug正常,Release出错
Visual Studio下程序开发: 编译QT程序Debug正常,Release出错
913 0
Visual Studio下程序开发: 编译QT程序Debug正常,Release出错
|
IDE 开发工具 C语言
使用Visual Studio IDE编写程序时不显示窗口或窗口一闪而逝的解决方法
使用Visual Studio IDE编写程序时不显示窗口或窗口一闪而逝的解决方法
|
IDE API 开发工具
Visual Studio编辑器还原项目NuGet提示某 NuGet 程序包还原失败:找不到“xxx”版本的程序包“xxx”
Visual Studio编辑器还原项目NuGet提示某 NuGet 程序包还原失败:找不到“xxx”版本的程序包“xxx”
288 0
Visual Studio编辑器还原项目NuGet提示某 NuGet 程序包还原失败:找不到“xxx”版本的程序包“xxx”
|
C++ Windows
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(二)
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(二)
218 0
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(二)
|
C++ Windows
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(一)
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(一)
439 0
【Visual Studio】Visual Studio 2019 创建 Windows 控制台程序 ( 安装 ‘使用 C++ 的桌面开发‘ 组件 | 创建并运行 Windows 控制台程序 )(一)
|
C++ Windows
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )
574 0
【OpenGL】一、Visual Studio 2019 创建 Windows 桌面程序 ( Visual Studio Installer 安装 C++ 桌面开发库 | 创建桌面程序 )

热门文章

最新文章