How I can create own driver for variables in powershell - wpf

I whant to create driver for own variables in own scope.
For example $myown:name = value
I tried to do that with different values for -Root:
New-PSDrive -Name myown -PSProvider Variable -Root /myown/
But that driver works like variable:.
compare (ls myown:) (ls variable:)
# no changes
How to create psdriver for variables that will work not like variable: psdrive?
$variable:name and $myown:name must be different.
Edited1:
What I want to do
I'm going to create module for loading WPF forms from .xaml.
And all elements with x:Name property must be imported to new PSDriver named like loaded form.
Load-WPF form1.xaml -OutVariable form1
$form1 # loaded WPF form
# and all form's elements added to form1: provider
$form1:button1 # <Button x:Name="button1" ../>
$form1:textbox1 # <TextBox x:Name="textbox1" ../>

Related

Powershell GUI Freezing, even with runspace

I am creating a powershell script with a GUI, that copies user profiles from a selected source disk to a destination disk. I've created the GUI in XAML, with VS Community 2019.
The script works like this : you select the source disk, the destination disk, the user profile and the folders you want to copy. When you press the button "Start", it calls a function called Backup_data, where a runspace is created. In this runspace, there's just a litte Copy-Item, with as arguments what you've selected.
The script works fine, all the wanted items are correctly copied. The problem is that the GUI is freezing during the copy (no "not responding" message or whatever, it's just completly freezed ; can't click anywhere, can't move the window). I've seen that using runspaces would fix this problem, but it doesn't to me. Am I missing something ?
Here's the function Backup_Data:
Function BackupData {
##CREATE RUNSPACE
$PowerShell = [powershell]::Create()
[void]$PowerShell.AddScript( {
Param ($global:ReturnedDiskSource, $global:SelectedUser, $global:SelectedFolders, $global:ReturnedDiskDestination)
##SCRIPT BLOCK
foreach ($item in $global:SelectedFolders) {
Copy-Item -Path "$global:ReturnedDiskSource\Users\$global:SelectedUser\$item" -Destination "$global:ReturnedDiskDestination\Users\$global:SelectedUser\$item" -Force -Recurse
}
}).AddArgument($global:ReturnedDiskSource).AddArgument($global:SelectedUser).AddArgument($global:SelectedFolders).AddArgument($global:ReturnedDiskDestination)
#Invoke the command
$PowerShell.Invoke()
$PowerShell.Dispose()
}
The PowerShell SDK's PowerShell.Invoke() method is synchronous and therefore by design blocks while the script in the other runspace (thread) runs.
You must use the asynchronous PowerShell.BeginInvoke() method instead.
Simple example without WPF in the picture (see the bottom section for a WPF solution):
$ps = [powershell]::Create()
# Add the script and invoke it *asynchronously*
$asyncResult = $ps.AddScript({ Start-Sleep 3; 'done' }).BeginInvoke()
# Wait in a loop and check periodically if the script has completed.
Write-Host -NoNewline 'Doing other things..'
while (-not $asyncResult.IsCompleted) {
Write-Host -NoNewline .
Start-Sleep 1
}
Write-Host
# Get the script's success output.
"result: " + $ps.EndInvoke($asyncResult)
$ps.Dispose()
Note that there's a simpler alternative to using the PowerShell SDK: the ThreadJob module's Start-ThreadJob cmdlet, a thread-based alternative to the child-process-based regular background jobs started with Start-Job, that is compatible with all the other *-Job cmdlets.
Start-ThreadJob comes with PowerShell [Core] 7+, and can be installed from the PowerShell Gallery in Windows PowerShell (Install-Module ThreadJob).
# Requires module ThreadJob (preinstalled in v6+)
# Start the thread job, always asynchronously.
$threadJob = Start-ThreadJob { Start-Sleep 3; 'done' }
# Wait in a loop and check periodically if the job has terminated.
Write-Host -NoNewline 'Doing other things..'
while ($threadJob.State -notin 'Completed', 'Failed') {
Write-Host -NoNewline .
Start-Sleep 1
}
Write-Host
# Get the job's success output.
"result: " + ($threadJob | Receive-Job -Wait -AutoRemoveJob)
Complete example with WPF:
If, as in your case, the code needs to run from an event handler attached to a control in a WPF window, more work is needed, because Start-Sleep can not be used, since it blocks processing of GUI events and therefore freezes the window.
Unlike WinForms, which has a built-in method for processing pending GUI events on demand ([System.Windows.Forms.Application]::DoEvents(), WPF has no equivalent method, but it can be added manually, as shown in the DispatcherFrame documentation.
The following example:
Creates a window with two background-operation-launching buttons and corresponding status text boxes.
Uses the button-click event handlers to launch the background operations via Start-ThreadJob:
Note: Start-Job would work too, but that would run the code in a child process rather than a thread, which is much slower and has other important ramifications.
It also wouldn't be hard to adapt the example to use of the PowerShell SDK ([powershell]), but thread jobs are more PowerShell-idiomatic and are easier to manage, via the regular *-Job cmdlets.
Displays the WPF window non-modally and enters a custom event loop:
A custom DoEvents()-like function, DoWpfEvents, adapted from the DispatcherFrame documentation is called in each loop operation for GUI event processing.
Note: For WinForms code, you could simply call [System.Windows.Forms.Application]::DoEvents().
Additionally, the progress of the background thread jobs is monitored and output received is appended to the job-specific status text box. Completed jobs are cleaned up.
Note: Just as it would if you invoked the window modally (with .ShowModal()), the foreground thread and therefore the console session is blocked while the window is being displayed. The simplest way to avoid this is to run the code in a hidden child process instead; assuming that the code is in script wpfDemo.ps1:
# In PowerShell [Core] 7+, use `pwsh` instead of `powershell`
Start-Process -WindowStyle Hidden powershell '-noprofile -file wpfDemo.ps1'
You could also do this via the SDK, which would be faster, but it's much more verbose and cumbersome:
$runspace = [runspacefactory]::CreateRunspace() $runspace.ApartmentState = 'STA'; $runspace.Open(); $ps = [powershell]::Create(); $ps.Runspace = $runspace; $null = $ps.AddScript((Get-Content -Raw wpfDemo.ps1)).BeginInvoke()
Screenshot:
This sample screen shot shows one completed background operation, and one ongoing one (running them in parallel is supported); note how the button that launched the ongoing operation is disabled for the duration of the operation, to prevent re-entry:
Source code:
using namespace System.Windows
using namespace System.Windows.Threading
# Load WPF assemblies.
Add-Type -AssemblyName PresentationCore, PresentationFramework
# Define the XAML document, containing a pair of background-operation-launching
# buttons plus associated status text boxes.
[xml] $xaml = #"
<Window
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:Test"
Title="MainWindow" Height="220" Width="600">
<Grid>
<TextBox x:Name="Status1" Height="140" Width="280" Margin="10,10" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Left" AcceptsReturn="True" AcceptsTab="True" Padding="4" VerticalScrollBarVisibility="Auto" />
<TextBox x:Name="Status2" Height="140" Width="280" Margin="10,10" TextWrapping="Wrap" VerticalAlignment="Top" HorizontalAlignment="Right" AcceptsReturn="True" AcceptsTab="True" Padding="4" VerticalScrollBarVisibility="Auto" />
<Button x:Name="DoThing1" Content="Do Thing 1" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="100" Height="22" Margin="10,5" IsDefault="True" />
<Button x:Name="DoThing2" Content="Do Thing 2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="100" Height="22" Margin="10,5" />
</Grid>
</Window>
"#
# Parse the XAML, which returns a [System.Windows.Window] instance.
$Window = [Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
# Save the window's relevant controls in PowerShell variables.
# Background-operation-launching buttons.
$btns = $Window.FindName('DoThing1'), $Window.FindName('DoThing2')
# Use a [hashtable] to map the buttons to the associated status text boxes.
$txtBoxes = #{
$btns[0] = $Window.FindName('Status1')
$btns[1] = $Window.FindName('Status2')
}
# Use a [hashtable] to map the buttons to the associated background
# operations, defined as script blocks to be passed to Start-ThreadJob later.
# The sample operations here run for a few seconds,
# emitting '.' every second and a message on completion.
$scriptBlocks = #{
$btns[0] =
{
1..3 | ForEach-Object { '.'; Start-Sleep 1 }
'Thing 1 is done.'
}
$btns[1] =
{
1..2 | ForEach-Object { '.'; Start-Sleep 1 }
'Thing 2 is done.'
}
}
# Attach the button-click event handlers that
# launch the background operations (thread jobs).
foreach ($btn in $btns) {
$btn.Add_Click({
# Temporarily disable this button to prevent re-entry.
$this.IsEnabled = $false
# Show a status message in the associated text box.
$txtBoxes[$this].Text = "Started thing $($this.Name -replace '\D') at $(Get-Date -Format T)."
# Asynchronously start a background thread job named for this button.
# Note: Would work with Start-Job too, but that runs the code in *child process*,
# which is much slower and has other implications.
$null = Start-ThreadJob -Name $this.Name $scriptBlocks[$this]
})
}
# Define a custom DoEvents()-like function that processes GUI WPF events and can be
# called in a custom event loop in the foreground thread.
# Adapted from: https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatcherframe
function DoWpfEvents {
[DispatcherFrame] $frame = [DispatcherFrame]::new($True)
$null = [Dispatcher]::CurrentDispatcher.BeginInvoke(
'Background',
[DispatcherOperationCallback] {
param([object] $f)
($f -as [DispatcherFrame]).Continue = $false
return $null
},
$frame)
[Dispatcher]::PushFrame($frame)
}
# Finally, display the window NON-modally...
$Window.Show()
$null = $Windows.Activate() # Ensures that the window gets the focus.
# ... and enter a custom event loop based on calling the custom .DoEvents() method
while ($Window.IsVisible) {
# Process GUI events.
DoWpfEvents
# Process pending background (thread) jobs, if any.
Get-Job | ForEach-Object {
# Get the originating button via the job name.
$btn = $Window.FindName($_.Name)
# Get the corresponding status text box.
$txtBox = $txtBoxes[$btn]
# Test if the job has terminated.
$completed = $_.State -in 'Completed', 'Failed', 'Stopped'
# Append any new results to the respective status text boxes.
# Note the use of redirection *>&1 to capture ALL streams, notably including the error stream.
if ($data = Receive-Job $_ *>&1) {
$txtBox.Text += "`n" + ($data -join "`n")
}
# Clean up, if the job is completed.
if ($completed) {
Remove-Job $_
$btn.IsEnabled = $true # re-enable the button.
$txtBox.Text += "`nJob terminated on: $(Get-Date -Format T); status: $($_.State)."
}
}
# Note: If there are no GUI events pending, this loop will cycle very rapidly.
# To mitigate this, we *also* sleep a little, but short enough to still keep
# the GUI responsive.
Start-Sleep -Milliseconds 50
}
# Window was closed; clean up:
# If the window was closed before all jobs completed,
# get the incomplete jobs' remaining output, wait for them to finish, and delete them.
Get-Job | Receive-Job -Wait -AutoRemoveJob
I've been searching for a solution all day and I've finally found one, so I'm gonna post it there for those who have the same problem.
First, check this article : https://smsagent.blog/2015/09/07/powershell-tip-utilizing-runspaces-for-responsive-wpf-gui-applications/
It's well explained and shows you how to correctly use runspaces with a WPF GUI. You just have to replace your $Window variable by $Synchhash.Window :
$syncHash = [hashtable]::Synchronized(#{})
$reader = (New-Object System.Xml.XmlNodeReader $xaml)
$syncHash.window = [Windows.Markup.XamlReader]::Load( $reader )
Insert a runspace function with your code :
function RunspaceBackupData {
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
$Runspace.SessionStateProxy.SetVariable("SelectedFolders",$global:SelectedFolders)
$Runspace.SessionStateProxy.SetVariable("SelectedUser",$global:SelectedUser)
$Runspace.SessionStateProxy.SetVariable("ReturnedDiskSource",$global:ReturnedDiskSource)
$Runspace.SessionStateProxy.SetVariable("ReturnedDiskDestination",$global:ReturnedDiskDestination)
$code = {
foreach ($item in $global:SelectedFolders) {
copy-item -Path "$global:ReturnedDiskSource\Users\$global:SelectedUser\$item" -Destination "$global:ReturnedDiskDestination\Users\$global:SelectedUser\$item" -Force -Recurse
}
}
$PSinstance = [powershell]::Create().AddScript($Code)
$PSinstance.Runspace = $Runspace
$job = $PSinstance.BeginInvoke()
}
And call it in the event-handler you want with the parameters you've indicated :
$var_btnStart.Add_Click( {
RunspaceBackupData -syncHash $syncHash -SelectedFolders $global:SelectedFolders -SelectedUser $global:SelectedUser -ReturnedDiskSource $global:ReturnedDiskSource -ReturnedDiskDestination $global:ReturnedDiskDestination
})
Don't forget to end your runspace :
$syncHash.window.ShowDialog()
$Runspace.Close()
$Runspace.Dispose()

Powershell winform event handlers in classes cause scope trouble

I'm experimenting with creating GUIs and using classes in powershell. I'm really new to both of those things (and to a lesser extent powershell generally) so bear with me.
The problem I am having is I cannot make any control which makes any modification to the form. This is because when adding a handler to a button it goes into the scope of the button class in the handler and none of the form references are accessible.
Most examples of UI code in powershell are not class heavy. I realize that I could get around this, if it was not in a class, by having the handlers and form being in the global scope, but I'm trying to make use of classes so that I have the ability to make base forms and inherit from them. And I want to see what is possible.
Below is some test code including multiple of my attempts to make this work with the results commented. I even got the idea of passing the form reference into the handler (DI style). Things I'm trying are all over the map since I'm also feeling out basic powershell syntax.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
class Window : System.Windows.Forms.Form
{
Handler () {
Write-Host Handler
$this.BackColor = [System.Drawing.Color]::Blue
}
HandlerArgs ([object]$sender, [System.Eventargs]$eventArgs) {
Write-Host HandlerArgs
$this.BackColor = [System.Drawing.Color]::Blue
}
$HandlerVar = {
Write-Host HandlerVar
$this.BackColor = [System.Drawing.Color]::Blue
}
HandlerParam ($form) {
Write-Host HandlerParam
$form.BackColor = [System.Drawing.Color]::Blue
}
$HandlerVarParam = {
(params $form)
Write-Host HandlerVarParam
$form.BackColor = [System.Drawing.Color]::Blue
}
Window ()
{
$button = New-Object System.Windows.Forms.Button
$button.Text = "ClickMe"
$button.AutoSize = $true
$this.Controls.Add($button)
# $button.Add_Click( $this.Handler )
# "Cannot convert argument "value", with value: "void Handler()", for "add_Click"
# to type "System.EventHandler": "Cannot convert the "void SelectNextPage()"
# value of type "System.Management.Automation.PSMethod" to type "System.EventHandler"."
# $button.Add_Click(([System.EventHandler]$x = $this.Handler ))
# turns the window blue immediatly
# $button.Add_Click( $this.HandlerArgs )
# "Cannot convert the "void HandlerArgs(System.Object sender, System.EventArgs eventArgs)"
# value of type "System.Management.Automation.PSMethod" to type "System.EventHandler".""
# $button.Add_Click( $this.HandlerVar )
# this works but turns the button blue instead of the form
# $button.Add_Click( { $this.Handler } )
# does nothing?
# $button.Add_Click( { $this.Handler() } )
# Method invocation failed because [System.Windows.Forms.Button] does not contain a
# method named 'Handler'.
# $button.Add_Click( $this.HandlerParam($this) )
# turns the window blue immediatly
# $button.Add_Click( { $this.HandlerParam($this) } )
# Method invocation failed because [System.Windows.Forms.Button] does not contain a
# method named 'HandlerParam'.
# $button.Add_Click( $this.HandlerVarParam $this )
# parse error
# I can't find a syntax that lets me pass a param to a function in a variable
}
}
$foo = New-Object Window
$foo.ShowDialog()
Although it's likely super obvious already, c# is my main language.
Perhaps this is just a limitation of the OO support in an interpreted scripting language, or maybe it's just my syntax deficiency. Is there any pattern that will get me what I want in this class-based structure? I would hope the pattern would be a general solution for doing normal form-things with handlers of form-controls.
Although mklement0 is absolutely spot on - class method can't be used directly as event delegates - there is a way to bind the instance to it without storing the handler in a property.
The following approach fails because $this resolves to the event owner at runtime:
$button.add_Click( { $this.Handler() } )
# Method invocation failed because [System.Windows.Forms.Button] does not contain a
# method named 'Handler'.
You can bypass this late-binding behavior by using any other (non-automatic) local variable to reference $this and then closing over it before calling add_Click():
$thisForm = $this
$button.add_Click( { $thisForm.Handler() }.GetNewClosure() )
# or
$button.add_Click( { $thisForm.HandlerArgs($this,$EventArgs) }.GetNewClosure() )
Now, $thisForm will resolve to whatever $this referenced when the handler was added, and the button will work as expected.
PowerShell, as of PowerShell 7.1, only knows how to pass script blocks as event delegates, not custom-class methods:
Mathias R. Jessen's helpful answer shows how to work around this limitation:
By wrapping a call to the event-handler method in a script block...
... and additionally providing a closure that captures a variable that refers to the class instance usually accessible as $this under a different name, to work around $this inside the script block referring to the event-originating object instead (same as the first handler argument, $sender), so as to ensure that access to the class instance remains possible.
As an aside: in this particular case, a solution without a .GetNewClosure() call would have been possible too, by calling $this.FindForm().
The following defines an idiom for generalizing this approach by encapsulating via a single helper method, which may be of interest if you have multiple event handlers in your class.:
As in your original approach and in Mathias' solution, individual event handlers are defined as you would normally define them in C#, as instance methods.
An auxiliary GetHandler() method encapsulates the logic of wrapping a call to a given event-handler method in a script block so that it is accepted by .add_{Event}() calls.
Any .add_{EventName}() call must then be passed the event-handler method via this auxiliary $this.GetHandler() method, as shown below.
Add-Type -AssemblyName System.Windows.Forms
class Window : System.Windows.Forms.Form {
# Define the event handler as an instance method, as you would in C#.
hidden ClickHandler([object] $sender, [EventArgs] $eventArgs) {
# Diagnostically print info about the sender and the event arguments.
$sender.GetType().FullName | Write-Verbose -vb
$eventArgs | Format-List | Out-String | Write-Verbose -vb
$this.BackColor = [System.Drawing.Color]::Blue
}
# Define a generic helper method that takes an event-handling instance method
# and turns it into a script block, so that it is accepted by
# .add_{Event}() calls.
hidden [scriptblock] GetHandler([Management.Automation.PSMethod] $method) {
# Wrap the event-handling method in a script block, because only a
# script block can be passed to .add_{Event}() calls.
# The .GetNewClosure() call is necessary to ensure that the $method variable
# is available when the script block is called by the event.
# Calling via a *method* also ensures that the method body still sees $this
# as the enclosing class instance, whereas the script block itself sees
# $this as the event-originating object (same as $sender).
return {
param([object] $sender, [EventArgs] $eventArgs)
$method.Invoke($sender, $eventArgs)
}.GetNewClosure()
}
# Constructor.
Window() {
$button = New-Object System.Windows.Forms.Button
$button.Text = "ClickMe"
$button.AutoSize = $true
$this.Controls.Add($button)
# Pass the event-handler method via the GetHandler() helper method.
$button.add_Click( $this.GetHandler($this.ClickHandler) )
}
}
$foo = New-Object Window
$foo.ShowDialog()
Note:
Add-Type -AssemblyName System.Windows.Forms must actually be executed before the script loads in order for you custom class definition deriving from System.Windows.Forms.Form to work, which is a known problem:
Only if the assembly containing the type that a custom PS class derives from has already been loaded at script parse time does the class definition succeed - see GitHub issue #3641.
The same applies to the using assembly statement - see about_Using, (which in PowerShell [Core] as of 7.0 has the added problem of not recognizing well-known assemblies such as System.Windows.Forms - see GitHub issue #11856)
In general, support for custom classes in PowerShell is, unfortunately, very much a work in progress, and many existing issues are tracked in GitHub meta issue #6652.

Winform application Add data to list view after certain variable contains data

Is it possible to add data to a listview after a certain variable contains data ?
I have a function, which gets info from ConfigMgr when I press on a button.
After I press on that button, some info will be stored in a variable called $Results.
I want the winform listview to wait until the variable $Results contains data, and then load my function, that will add data to my listview - is that possible ?
It's working fine if I don't clear the variable $Results, and run the winform a second time, because then the variable $Results is not empty,
by having$Form.Add_Shown( { $Form.Activate(); Results }) in my script
Is there an equivalent method to achieve what I want ?
This is my function:
Function Get-SKUNotExists {
#Get objects stored in $Results
$listview_NotExists_SKU_Results = $Results | Select-Object Name,ID
# Compile a list of the properties
$listview_NotExists_SKU_Properties = $listview_NotExists_SKU_Results[0].psObject.Properties
# Create a column in the listView for each property
$listview_NotExists_SKU_Properties | ForEach-Object {
$listview_NotExists_SKU.Columns.Add("$($_.Name)")
}
# Looping through each object in the array, and add a row for each
ForEach ($listview_NotExists_SKU_Result in $listview_NotExists_SKU_Results) {
# Create a listViewItem, and assign it it's first value
$listview_NotExists_SKU_Item = New-Object System.Windows.Forms.ListViewItem($listview_NotExists_SKU_Result.Name)
# For each properties, except for 'Id' that we've already used to create the ListViewItem,
# find the column name, and extract the data for that property on the current object/Tasksequence
$listview_NotExists_SKU_Result.psObject.Properties | Where-Object { $_.Name -ne "ID" } | ForEach-Object {
$listview_NotExists_SKU_Item.SubItems.Add("$($listview_NotExists_SKU_Result.$($_.Name))")
}
# Add the created listViewItem to the ListView control
# (not adding 'Out-Null' at the end of the line will result in numbers outputred to the console)
$listview_NotExists_SKU.items.Add($listview_NotExists_SKU_Item)
}
# Resize all columns of the listView to fit their contents
$listview_NotExists_SKU.AutoResizeColumns("HeaderSize")
}
This is the button that generate data to $results:
# Adding another button control to Form
$button_whatif = New-Object System.Windows.Forms.Button
$button_whatif.Location = New-Object System.Drawing.Size(352, 954)
$button_whatif.Size = New-Object System.Drawing.Size(320, 32)
$button_whatif.TextAlign = "MiddleCenter"
$button_whatif.Text = “WhatIf”
$button_whatif.Add_Click( { $script:Results = Set-DynamicVariables -Manufacturer "$($listview_Vendor.SelectedItems)" -TSPackageID "$($ListView_Tasksequences.SelectedItems.SubItems[1].Text)" -WhatIf })
$Form.Controls.Add($button_whatif)
Simply call your list-populating function (Get-SKUNotExists) directly after assigning to $Results:
$button_whatif.Add_Click({
$Results = Set-DynamicVariables -Manufacturer "$($listview_Vendor.SelectedItems)" -TSPackageID "$($ListView_Tasksequences.SelectedItems.SubItems[1].Text)" -WhatIf
Get-SKUNotExists
})
Note that I've removed the $scipt: scope specifier from $Results, since it is no longer needed, given that you're calling Get-SKUNotExists from the same scope, which means that the child scope that Get-SKUNotExists runs in implicitly sees it.
That said:
In general, if a value is directly available, it's more robust to pass it as a parameter (argument) rather than relying on PowerShell's dynamic scoping (where descendant scopes implicitly see variables from ancestral scopes, unless shadowed by local variables).
If values must be shared between multiple event handlers, storing them in the script scope ($script:...), or, more generally, the parent scope may be needed, after all - see this answer.

Event listener that will pull data from a selected column in a ListView PS

As title says, I need help to create an event listener. I've searched for other answers, but they don't really help me, and I'm new to programming so I am struggling with this.
The purpose of the script is to search for scripts on the local system, pull information about them, and then display it on a GUI interface. From the interface, you would select the different displayed scripts, and be able to run the selected script, get script notes from the corresponding text file for the script, and also open the file path in file explorer.
The first section of my code is this:
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase
[Xml]$xaml = (design code omitted)
$xmlNodeReader = New-Object System.Xml.XmlNodeReader($xaml)
$Window = [System.Windows.Markup.XamlReader]::Load($xmlNodeReader)
This is the base for the rest of my stuff, which I haven't been able to find much help with. The next section of code is the header to prepare to fill the columns.
$values = Get-ChildItem -Path C:\(Directory) -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue -Force
$ListView = $Window.FindName("OutputList")
# outputlist is the name of the listview in the xaml #
(further down would be the foreach loop to create the columns and register the data)
$ListView.ItemsSource = $values
Past that, I have nothing. I can't figure out how to create the listener for the selected item on the ListView. If I can do that, I should be able to grab the data and be set.
From other questions, WPF listview has a SelectionChanged event, and from FoxDeploy's WPF GUIs in PowerShell series, Part IV, the way to add event listeners is like so:
$ListView.Add_SelectionChanged({
# code here
})
Where the {} is a PowerShell scriptblock

How to create a file screen exception in powershell (FSRM Api)

I need to create file screen exception in powershell using the FSRM Api, I am using this script to create the cuota but I am having trouble to commit the object.
Because I haven't achieved to meet the requirement to modify AllowedFileGroups property :(
$FSRMObject = New-Object -Com Fsrm.FsrmFilescreenManager
$createFileScreenException = $FSRMObject.CreateFileScreenException("c:\")
$createFileScreenException.AllowedFileGroups("Text Files")
$createFileScreenException.Commit()
This is what I get Listing the Properties and Methods of the Object, in the property definition of AllowedFileGroups I can see that I need to create IFsrmMutableCollection.
Does anyone have an idea of how to create the file screen exception?
AllowedFileGroups is a property, not a method, so I'd expect something like this to work:
$createFileScreenException = $FSRMObject.CreateFileScreenException('c:\')
$createFileScreenException.AllowedFileGroups = 'Text Files'
$createFileScreenException.Commit()
Can't test it, though.
This is how you can create the simplest quota using the FSRM api in powershell, to view more modificable options get the members of the object $quota.
$fsrmQuotaObject = New-Object -Com FSrm.FsrmQuotaManager
$quota = $fsrmQuotaObject.CreateQuota("c:\path")
$quota.ApplyTemplate("Select template")
$quota.Commit()

Resources