I have a series of AD domains, each domain having it's own admin accounts.
I would like to be able to open ADUC from within PowerShell for each domain using the correct credentials.
Because of domain trust setup I can't use the -credential switch when starting a new PS instance so runas /netonly seems to be the best option.
Now this works fine in Powershell - until a GUI front end is added using WPF - the reason being I suspect that PS is trying to use the existing window to display the user/pass prompt but can't because it's tied to a window.
For some reason I can't get it to spawn a new window, even by using start-process. (The start process command below worked fine under PS2 but since upgrading to PS4 it now fails telling me that -STA and -Command aren't real parameters)
I'm using the xaml converter from http://blogs.technet.com/b/heyscriptingguy/archive/2014/08/01/i-39-ve-got-a-powershell-secret-adding-a-gui-to-scripts.aspx to load a xaml form:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UAT" Height="300" Width="300">
<Grid>
<Button Name="btnGo" Content="Go" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="207,240,0,0"/>
<TextBox Name="txtOut" HorizontalAlignment="Left" Height="233" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="272" Margin="10,0,0,0"/>
</Grid>
</Window>
The .PS1 file
$c = "admin-" + [Environment]::UserName
$server = "foo.bar"
.\loadDialog.ps1 -XamlPath '.\UAT.xaml'
$btnGo.add_Click({
#Start-Process PowerShell -STA -Command {runas /netonly /user:DOMAIN\$c "mmc.exe dsa.msc /server=$server"}
& runas /netonly /user:DOMAIN\$c "mmc.exe dsa.msc /server=$server"
})
$xamGUI.ShowDialog() | out-null 2>> Errors.txt
Is there another way to start this process in such a way that it will spawn it's own window?
Related
I am creating a small tool which is split into 2 files:
Form.ps1 - GUI & Button Events
Main.ps1 - All functions
The form itself look likes the following:
When a user is entered and Find User is clicked, if there is only 1 result, then the username and approver are filled (approver is the users line manager) as follows:
However, if more than 1 result is returned, it will then create a listbox of the results.
The part I am struggling with is how to add the approver as well as the username using $listbox.SelectedItem
At the moment I can only get the username like the following example:
I am using the following AD Query to get the Name, Email, Manager and ManagersEmail:
$searchResult = Get-ADUser -Filter "givenName -like ""$Firstname"" -and sn -Like ""$Surname""" -Properties Name, Mail, Manager |
Where { $_.Enabled -eq $True} |
Select-Object Name, Mail, #{Name="ManagersEmail";Expression={(get-aduser -property mail $_.manager).mail}}
The variables $firstname and $surname are taken from the textbox using a split
You can use WPF's element binding feature
I have filled the ListBox1 with Items of string type, therfore it is binds directly no need to get the object's property.
Below is the sample XAML & Code.
XAML:-
<TextBox Text="{Binding ElementName=listbox1, Path=SelectedItem}"
HorizontalAlignment="Left" Height="24" Margin="164,100,0,0"
TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<ListBox x:Name="listbox1"
HorizontalAlignment="Left"
Height="100"
Margin="164,156,0,0"
VerticalAlignment="Top"
Width="100"/>
C#:- Code behind file.
listbox1.Items.Add("ABC");
listbox1.Items.Add("XyZ");
listbox1.Items.Add("123");
You can skip this C# part because you might already fill it by clicking on the Find User button.
I've ran into an issue while using WPF. When I open the window, I want it to open up on top of all other programs but I don't want it to remain always on top, just the initial opening. I know setting topmost to true will open up on top (which I have in the Xaml), but I can't seem to find a way to change it to false after it has opened.
This is a simple test function with a WPF window.
function foo{
#Load Assembly and Library
Add-Type -AssemblyName PresentationFramework
$inputXaml = #"
<Window x:Class="SharepointCreateOpportunity.completeWindow"
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:SharepointCreateOpportunity"
mc:Ignorable="d"
Title="Window Title" Height="250" MinHeight="250" MaxHeight="250" Width="500" MinWidth="500" MaxWidth="500" Topmost="True" WindowStartupLocation="CenterScreen" Background="Black" >
<Grid>
<Button Name="oKBtn" Content="OK" Margin="0,0,20,20" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="72" Height="23"/>
</Grid>
</Window>
"#
#Gets rid of elements from the Xaml so it can be converted
$inputXamlClean = $inputXaml -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace 'x:Class=".*?"','' -replace 'd:DesignHeight="\d*?"','' -replace 'd:DesignWidth="\d*?"',''
[xml]$xaml = $inputXamlClean
#Creates the Window
$XMLReader = (New-Object System.Xml.XmlNodeReader $xaml)
$Window = [Windows.Markup.XamlReader]::Load($XMLReader)
#Creates variables for all the elements on the window
$xaml.SelectNodes("//*[#Name]") | %{Set-Variable -Name ($_.Name) -Value $Window.FindName($_.Name)}
#OK Button Action
$okBtn.Add_Click({
$Window.Close()
})
#Show window
$Window.ShowDialog()
}
foo
If Topmost is not set in the XAML, the window will appear on top when the script is run (assuming there are no other windows with Topmost set) and will behave like a normal desktop window where focusing on other windows with the mouse or keyboard will cause the window to lose foreground position.
So for the window to be on top for "just the initial opening," getting rid of the Topmost property or setting it to false will work, again assuming there are no other windows with Topmost set. If there are in fact other windows vying for the topmost position, you could use SetForegroundWindow to put it temporarily to the top:
https://stackoverflow.com/a/12802050/4195823
I'm looking for help for my WPF form script, I'm creating using powershell with WPF form. I am trying to get command output of Powershell exchange online cmdlets when i click submit button, for example Get-CASMailbox. it seems like it's not connecting in the same session or something. If I start up a powershell window and put in the commands to connect then i was able to get the details, however when i tried to run the script and click submit button in the form,nothing is happening. Can anyone help me figure out where I'm having a problem?
Here is the code I'm using
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Confirm:$false -Force
Add-Type -AssemblyName presentationframework, presentationcore
Add-Type -AssemblyName WindowsBase
$wpf = #{ }
Function Connect-ExchangeOnline {
$msolcred = Get-Credential
Set-ExecutionPolicy Unrestricted
$Global:Session365 = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-LiveID/ -Credential $msolcred -Authentication Basic -AllowRedirection
Import-PSSession $global:Session365 -AllowClobber
}
[xml]$Form = #"
<Window Name="AzureAD"
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:WpfApp5"
Title="AzureAD Tool" Height="470" Width="800" Topmost="True">
<StackPanel>
<Grid>
<Button x:Name="Submit" Content="Submit" Width="129" Margin="4,6,4,2" FontWeight="Bold" />
</Grid>
</StackPanel>
</Window>
"#
#Create a form
$Global:xmlReader = New-Object System.Xml.XmlNodeReader $Form
$Global:xmlform = [Windows.Markup.xamlReader]::Load($xmlReader)
$Global:namedNodes = $Form.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]")
$Global:namedNodes | ForEach-Object {$wpf.Add($_.Name,
$xmlform.Findname($_.Name))}
$wpf.Submit.Add_Click({
Connect-ExchangeOnline
Get-CASMailbox
})
$wpf.AzureAD.ShowDialog()
#Get-CASMailbox
Remove-PSSession -ComputerName outlook.office365.com
i figured it out that, for some reason when i call the Get-CASMailbox with WPF its not getting any output, but when i add "Get-CASMailbox | out-host" i was able to get it..
I was following along with the guide located here trying to make a GUI with PowerShell, and it all goes well except I have a DataGrid that needs to be populated in my GUI.
In my XML grid I have:
[xml]$form=#"
<Window
[...]
<Grid>
[...]
<DataGrid Name="CSVGrid" HorizontalAlignment="Left" Height="340" Margin="10,60,0,0" VerticalAlignment="Top" Width="765"/>
[...]
</Grid>
</Window>
"#
Now in the tutorial to create the form it uses the following:
$XMLReader = (New-Object System.Xml.XmlNodeReader $Form)
$XMLForm = [Windows.Markup.XamlReader]::Load($XMLReader)
But to get my DataGrid to work, I believe I need to define my "CSVGrid" DataGrid as "system.Windows.Forms.DataGridView" somewhere, but I'm not sure how to tie that together. Running it without defining this will spit out errors if I try to invoke any DataGrid properties, like setting column amounts or column names.
Any ideas?
The way POSHGUI implements their forms actually works perfectly for my purpose, but I prefer editing the WPF forms in Visual Studio. If need be, I can rebuild the form in POSHGUI but hopefully there's a way to tie this together here so I can continue to use the VS GUI for editing the form's GUI.
Edit: It should be noted there's not just the data grid on the form, in case that wasn't clear.
Edit 2: As an extra bit of info, the way POSHGUI formats the controls is like so:
#the form itself
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '400,400'
$Form.text = "Form"
$Form.TopMost = $false
#a datagrid
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.width = 382
$DataGridView1.height = 335
$DataGridView1.location = New-Object System.Drawing.Point(8,55)
#a button
$Button1 = New-Object system.Windows.Forms.Button
$Button1.text = "My button"
$Button1.width = 126
$Button1.height = 30
$Button1.location = New-Object System.Drawing.Point(156,13)
$Button1.Font = 'Microsoft Sans Serif,10'
It then ties them together with:
$Form.controls.AddRange(#($DataGridView1,$Button1))
So that happily defines the DataGrid variable as a "system.Windows.Forms.DataGridView", &c, whereas the method of putting the entire XML into a $variable and passing that into a "System.Xml.XmlNodeReader" doesn't make the distinction I don't believe, which is why I'm unable to call any DataGrid properties.
But again, I'd rather create the GUI in Visual Studio if I can...
Edit 3: If it helps at all, checking the intellisense dropdown, various DataGrid properties are there, but not ColumnCount for example:
So maybe it's working as intended? But at the same time, if my DataGrid variable is defined explicitly as a system.Windows.Forms.DataGridView, like in the POSHGUI example, then setting ColumnCount works flawlessly...
Before we get too far into it, you can't use a DataGridView in WPF (which is what I'll be showing you here). You can, however, use a DataGrid instead which is pretty much the same thing (and wayyyy shorter and easier in PowerShell than Windows Forms, which is what POSHGUI uses (awesome tool though it is).
If you're still interested though, here's a super basic walkthrough of how you could do this. First, to define the XAML
$inputXML = #"
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid Name="Datagrid" AutoGenerateColumns="True" >
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="180" />
<DataGridTextColumn Header="Type" Binding="{Binding Type}" Width="233"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
"#
Note that we are defining which columns from our input object we wanna display. Haven't found a great way to automatically create them yet.
Next, load the form (using the method from my walkthrough on the topic here)
$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 [System.Management.Automation.MethodInvocationException] {
Write-Warning "We ran into a problem with the XAML code. Check the syntax for this control..."
write-host $error[0].Exception.Message -ForegroundColor Red
if ($error[0].Exception.Message -like "*button*"){
write-warning "Ensure your <button in the `$inputXML does NOT have a Click=ButtonClick property. PS can't handle this`n`n`n`n"}
}
catch{#if it broke some other way <span class="wp-smiley wp-emoji wp-emoji-bigsmile" title=":D">:D</span>
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)}
Now, to add some items to this DataGridView. The above code also gives us a variable titled $WPFGridView in our session. We can add child items to our DataGridView by calling the .AddChild() method of that variable and feeding it items that have at least the same properties as we defined in our DataGrid earlier.
$WPFDatagrid.AddChild([pscustomobject]#{Name='Stephen';Type=123})
$WPFDatagrid.AddChild([pscustomobject]#{Name='Geralt';Type=234})
Then, to display our GUI, we call the $Form's ShowDialog() method like so, and then the bottom should greet us.
$Form.ShowDialog()
Full code sample is available here. If you want to learn more about using WPF GUI elements like this, I have a full walkthrough available here.
I modified it a bit:
is a function (allows for multiple windows)
returns an array of the elements
loads the presentation framework (supports x:Bind as Binding)
you can create any WPF form with visual studio and load it (mostly, do not forget any extra classes used to be defined within the XAML and maybe load its type in posh)
A more advanced version: supporting click handler
function LoadXamlForm
{
Param
(
[Parameter(Mandatory=$true)] $xamlFile
)
Add-Type -AssemblyName PresentationFramework
$inputXML = Get-Content -Path $xamlFile
# https://stackoverflow.com/a/52416973/1644202
$inputXML = $inputXML -replace 'mc:Ignorable="d"','' -replace "x:N",'N' -replace '^<Win.*', '<Window' -replace "x:Bind", "Binding"
[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 [System.Management.Automation.MethodInvocationException] {
Write-Warning "We ran into a problem with the XAML code. Check the syntax for this control..."
write-host $error[0].Exception.Message -ForegroundColor Red
if ($error[0].Exception.Message -like "*button*"){
write-warning "Ensure your <button in the `$inputXML does NOT have a Click=ButtonClick property. PS can't handle this`n`n`n`n"}
}
catch{#if it broke some other way <span class="wp-smiley wp-emoji wp-emoji-bigsmile" title=":D">:D</span>
Write-Host "Unable to load Windows.Markup.XamlReader. Double-check syntax and ensure .net is installed."
}
#===========================================================================
# Store Form Objects In PowerShell
#===========================================================================
$Elements = #{}
$xaml.SelectNodes("//*[#Name]") | %{ $Elements[$_.Name] = $Form.FindName($_.Name) }
return $Form, $Elements
}
$Form,$Elements = LoadXamlForm .\gui.xaml
$Elements.lvApps.AddChild([pscustomobject]#{Name='Ben';Description="sdsd 343"})
$Form.ShowDialog() | out-null
just any xaml - i know this is no datagrid - but gives you a use case nonetheless
<Window x:Class="WpfApp.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListView SelectionMode="Multiple" x:Name="lvApps" Height="371" VerticalAlignment="Top">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="64" AutomationProperties.Name="{Binding Name}">
<Ellipse Height="48" Width="48" VerticalAlignment="Center">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding ButtonImage}"/>
</Ellipse.Fill>
</Ellipse>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" Margin="12,0,0,0">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Description}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Window>
I've just a little problem I guess.
I created a GUI with WPF for Powershell, and in this GUI is a passwordbox, now I would like to get the clear value from this passwordbox into a variable in PS.
<PasswordBox Name="PWBox" HorizontalAlignment="Left" Margin="147,75,0,0" VerticalAlignment="Top" Width="137" FontFamily="Century Gothic"/>
I tested e.g.:
$password = $PWBox.Password
But I get no Value in the $password
What is the right way to do it?
Thanks in advance,
Robin