Powershell - Voice Synth Save Output - powershell

I would like to help understand how I may save the results of a variable.method output into another variable.
I would like to test creating a song but I am stuck on how to proceed.
Currently the voice outputs itself but doesn't save in the variable.
Is their a logical reason why? I've only studied powershell for a month.
Add-Type -AssemblyName System.Speech
$speak = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$man = $speak.Speak("Male Voice") # Outputs the Audio to host, but it does not store the result into Variable
$speak.SelectVoice("Microsoft Zira Desktop")
$woman = $speak.Speak("Female Voice")# Same as above
function song {
$man
$woman
song
}

The Speak method has no return value
public void Speak( string textToSpeak )
However it is possible to set the audio input to a file via SetOutputToWaveFile
You can then use SoundPlayer to play the audio back immediatly for better user experience.
Add-Type -AssemblyName System.Speech
$soundFilePath = "Test.wav"
# Save the Audio to a file
[System.Speech.Synthesis.SpeechSynthesizer] $voice = $null
Try {
$voice = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$voice.SetOutputToWaveFile($soundFilePath)
$voice.Speak("Male Voice")
$voice.SelectVoice("Microsoft Zira Desktop")
$voice.Speak("Female Voice")
} Finally {
# Make sure the wave file is closed
if ($voice) {
$voice.Dispose()
}
}
# Load the saved audio and play it back
[System.Media.SoundPlayer] $player = $null
Try {
$player = New-Object -TypeName System.Media.SoundPlayer($soundFilePath)
$player.PlaySync()
} Finally {
if ($player) {
$player.Dispose()
}
}
If you only need the audio in memory and you don't want to post-process anything you could write the data to a MemoryStream instead.
Add-Type -AssemblyName System.Speech
[System.IO.MemoryStream] $memoryStream = $null;
Try {
$memoryStream = New-Object -TypeName System.IO.MemoryStream
[System.Speech.Synthesis.SpeechSynthesizer] $voice = $null
Try {
$voice = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$voice.SetOutputToWaveStream($memoryStream)
$voice.Speak("Male Voice")
$voice.SelectVoice("Microsoft Zira Desktop")
$voice.Speak("Female Voice")
} Finally {
if ($voice) {
$voice.Dispose()
}
}
# Load the saved audio and play it back
[System.Media.SoundPlayer] $player = $null
Try {
$memoryStream.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$player = New-Object -TypeName System.Media.SoundPlayer($memoryStream)
$player.PlaySync()
} Finally {
if ($player) {
$player.Dispose()
}
}
} Finally {
if ($memoryStream) {
$memoryStream.Dispose()
}
}

You can't put the voice output into a variable, because it's not a value returned by the function but a "side-effect".
But there's no need to store the audio. It's much more effective to just store the text and generate the voice output each time the script is run.
Edit: I used .SpeakAsync and sleep to have better control of the timing. Adjust the delay number so it fits the flow of each line.
Add-Type -AssemblyName System.Speech
$male = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$male.SelectVoice("Microsoft David Desktop")
$female = New-Object -TypeName System.Speech.Synthesis.SpeechSynthesizer
$female.SelectVoice("Microsoft Zira Desktop")
function male([string] $text, [int] $duration) {
$male.SpeakAsync($text) | Out-Null
sleep -Milliseconds $duration
}
function female([string] $text, [int] $duration) {
$female.SpeakAsync($text) | Out-Null
sleep -Milliseconds $duration
}
function song {
male 'hello' 500
female 'goodbye' 500
}
song

Related

Populate ComboBox2 depending on ComboBox1 selection

Few days of searching and trying multiple options, but nothing is working actually. With the previous code posted.
I've imported all my objects from XAML (all set in variables), I don't know if that would be a problem for that. I don't think since everything else seems to work properly.
I just want my Tab2CBB2 to show values depending on the selection of Tab1CBB1. Anyone could help ? (I haven't paste the entire code, neither the paths but you can probably help me with that). Note that those are two of my multiple tries. Thanks
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$xaml = #" ...
"#
#Read XAML (Parse it)
$reader=(New-Object System.Xml.XmlNodeReader $XAML)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$listQA14 = Get-ChildItem $pathQA14 -name
$listQA15 = Get-ChildItem $pathQA15 -name
$listDBQA = Get-ChildItem $pathDBQA -name
$Tab1CBB1_SelectedIndexChanged= {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
Switch ($Tab1CBB1.Text) {
'QA14' {
$ListQA14 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'QA15' {
$ListQA15 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'ARIELDBQA' {
$ListDBQA | ForEach { $Tab1CBB2.Items.Add($_) }
}
}
}
$Tab1CBB1.add_SelectedIndexChanged($Tab1CBB1_SelectedIndexChanged)
#Displays the Window
$Window.ShowDialog() | out-null
Here is another option tried :
#ComboBox1------
$ItemsCBB1 = #('ARIELDBQA','QA14','QA15')
foreach ($Item in $ItemsCBB1) {
$Tab1CBB1.Items.Add($Item)
}
#ComboBox2-------
if ($Tab1CBB1.SelectedItem -eq 'QA14') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA14) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'QA15') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA15) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'listDBQA') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listDBQA) {
$Tab1CBB2.Items.Add($Serie)
}
}
Tried this also : $Selection = $Tab1CBB1.SelectedItem.ToString()
Variable $selection put after 'if', but not working
Note that when I indicate what the current selection would be, it is working properly. The problem seems to come from 'recording' the selection a the time of clicking... Thnaks !
Basically, all you have to do is inside the $Tab1CBB1_SelectedIndexChanged scriptblock use the various lists with script scoping.
Without that, the variables are unknown inside the script block.
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $script:ListQA14 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'QA15' { $script:ListQA15 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'ARIELDBQA' { $script:ListDBQA | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
}
}
Another method could be to dynamically get the items to enter in the combobox, especially since these are file lists and can change while your form is being used:
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $path = $script:pathQA14 ; break }
'QA15' { $path = $script:pathQA15 ; break }
'ARIELDBQA' { $path = $script:pathDBQA }
}
Get-ChildItem -Path $path -Name | ForEach-Object { $Tab1CBB2.Items.Add($_) }
}
A simple example of what you seem to be after is something like this.
### Powershell populate ComboBox 2 based on ComboBox 1 Selection
Add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$labelDoubleClickOnAnyItem = New-Object 'System.Windows.Forms.Label'
$listbox2 = New-Object 'System.Windows.Forms.ListBox'
$listbox1 = New-Object 'System.Windows.Forms.ListBox'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$form1_Load={
$items=1..9 |
ForEach-Object {"List item $_"}
$listbox1.Items.AddRange($items)
}
$listbox1_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox2.Items.Add($listbox1.SelectedItem)
$listbox1.Items.Remove($listbox1.SelectedItem)
}
$listbox2_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox1.Items.Add($listbox2.SelectedItem)
$listbox2.Items.Remove($listbox2.SelectedItem)
}
$form1.Controls.Add($labelDoubleClickOnAnyItem)
$form1.Controls.Add($listbox2)
$form1.Controls.Add($listbox1)
$form1.Controls.Add($buttonOK)
$form1.AcceptButton = $buttonOK
$form1.ClientSize = '378, 388'
$form1.FormBorderStyle = 'FixedDialog'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_Load($form1_Load)
$labelDoubleClickOnAnyItem.Font = 'Microsoft Sans Serif, 9.75pt, style=Bold, Italic'
$labelDoubleClickOnAnyItem.Location = '13, 13'
$labelDoubleClickOnAnyItem.Name = 'labelDoubleClickOnAnyItem'
$labelDoubleClickOnAnyItem.Size = '353, 41'
$labelDoubleClickOnAnyItem.TabIndex = 3
$labelDoubleClickOnAnyItem.Text = 'Double click on any item to move it from one box to the other.'
$listbox2.FormattingEnabled = $True
$listbox2.Location = '200, 67'
$listbox2.Name = 'listbox2'
$listbox2.Size = '166, 277'
$listbox2.Sorted = $True
$listbox2.TabIndex = 2
$listbox2.add_MouseDoubleClick($listbox2_MouseDoubleClick)
$listbox1.FormattingEnabled = $True
$listbox1.Location = '12, 67'
$listbox1.Name = 'listbox1'
$listbox1.Size = '167, 277'
$listbox1.Sorted = $True
$listbox1.TabIndex = 1
$listbox1.add_MouseDoubleClick($listbox1_MouseDoubleClick)
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '291, 353'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$form1.ShowDialog()
Or even this using just the ComboBox elements
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(498,459)
$Form.text = 'Form'
$Form.TopMost = $false
$Form.StartPosition = 'CenterScreen'
$ComboBox1 = New-Object system.Windows.Forms.ComboBox
$ComboBox1.text = ''
$ComboBox1.width = 216
$ComboBox1.height = 75
#('ComboxItem1','ComboxItem2','ComboxItem3') |
ForEach-Object {[void] $ComboBox1.Items.Add($_)}
$ComboBox1.location = New-Object System.Drawing.Point(31,41)
$ComboBox1.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$ComboBox2 = New-Object system.Windows.Forms.ComboBox
$ComboBox2.text = ''
$ComboBox2.width = 213
$ComboBox2.height = 38
$ComboBox2.location = New-Object System.Drawing.Point(32,73)
$ComboBox2.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
Function Add-ItemToComboBox2
{
$ComboBox2.Items.Add($ComboBox1.SelectedItem)
$ComboBox2.Refresh()
}
$Form.controls.AddRange(
#(
$ComboBox1,
$ComboBox2
)
)
$ComboBox1.Add_SelectedIndexChanged({ Add-ItemToComboBox2 })
[void]$Form.ShowDialog()
You have not stated what UX/UI tool you are using to design and implement your
GUI. Yet, hard coding is also just a challenge. So consider using one of these for your UX/UI effort. Your UX/UI should just work, whether you have PowerShell code behind it (or any other language). Your PowerShell code should just work, regardless of the UX/UI that may be implemented. Your UX/UI code should be separate from your ops code. You call your ops code from the UX/UI.
Free:
https://poshgui.com
https://www.youtube.com/results?search_query=poshgui
https://visualstudio.microsoft.com/vs/community
Be sure to thoroughly read the licensing agreement for VS.
https://www.youtube.com/results?search_query=vs+2019+community+winforms
Purchase
https://ironmansoftware.com/psscriptpad
https://www.youtube.com/results?search_query=psscriptpad
https://ironmansoftware.com/powershell-pro-tools
https://www.youtube.com/results?search_query=powershell-pro-tools
https://www.sapien.com/software/powershell_studio
https://www.youtube.com/results?search_query=Sapien%27s+powershell+studio++winforms
https://visualstudio.microsoft.com/vs
https://www.youtube.com/results?search_query=vs+2019+community+winforms

Is there a way to loop a script so that it uses a different value with each pass through

I have a full script which works just fine. The issue is, I have a system of GUI's set up which are all used for varying roles and what I need is a select all function on one of these GUI's. For example;
###############################MONTH SELECTER############################################################
[array]$DropDownArrayItems = "","01","02","03","04","05","06","07","08","09","10","11","12", 'Select all months'
[array]$DropDownArray = $DropDownArrayItems | sort
function Return-DropDown {
if ($DropDown.SelectedItem -eq $null){
$DropDown.SelectedItem = $DropDown.Items[0]
$script:Choice = $DropDown.SelectedItem.ToString()
$Form.Close()
}
else{
$script:Choice = $DropDown.SelectedItem.ToString()
$Form.Close()
}
}
function SelectGroup{
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$Form = New-Object System.Windows.Forms.Form
$Form.width = 300
$Form.height = 150
$Form.Text = ”Select Filter Month”
$DropDown = new-object System.Windows.Forms.ComboBox
$DropDown.Location = new-object System.Drawing.Size(100,10)
$DropDown.Size = new-object System.Drawing.Size(130,30)
ForEach ($Item in $DropDownArray) {
[void] $DropDown.Items.Add($Item)
}
$Form.Controls.Add($DropDown)
$DropDownLabel = new-object System.Windows.Forms.Label
$DropDownLabel.Location = new-object System.Drawing.Size(10,10)
$DropDownLabel.size = new-object System.Drawing.Size(100,40)
$DropDownLabel.Text = "Select Month:"
$Form.Controls.Add($DropDownLabel)
$Button = new-object System.Windows.Forms.Button
$Button.Location = new-object System.Drawing.Size(100,50)
$Button.Size = new-object System.Drawing.Size(100,20)
$Button.Text = "OK"
$Button.Add_Click({Return-DropDown})
$form.Controls.Add($Button)
$form.ControlBox = $false
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
return $script:choice
}
$Group = $null
$Group = SelectGroup
while ($Group -like ""){
$Group = SelectGroup
}
###################################################################################
This selects a month and is used in things like file paths and filters the problem is, if someone wants data from every month, they have to manually repeat the script 12 times and answer all the GUI'S again.
I've tried;
if ($Group -eq 'Select all') {
1..12 | ForEach-Object { '{0:00}' -f $_ }
}
But the file path gets confused and things break so including a select all function and taking numbers 1-12 doesn't quite work.
So is there a way to loop the entire script such that with each pass through it selects a different month whilst keeping all other variables in the script the same if the 'select all months option is selected'
I don't think the problem that you're having is in the code you posted.
The easiest thing to do would be to take all the code past the Select Group, and move it into it's own function.
function DoWork { params ([int]$Month)
$Month = '{0:00}' -f $Month
Echo $Month
#Do your work- this is where you code past the UI goes
}
#This is where your drop down labels, etc goes.
if ($Group -eq 'Select All') {
1..12 | ForEach-Object {DoWork($_)}
} else {
DoWork($Group)
}
This will call your DoWork (or whatever you name it) function 12 times, each with a different $Month, if Select All is used.
Depending on variable scope- if your first run through changes a variable, your second run through might start with that changed variable. I'm worried that might be where "they file path gets confused" might be coming from.
If so, look at how you change the $Path variable- or whatever it is.

PowerShell readline from a stream

Using a PowerShell script I will have to read and write to a console. We write an input and wait for an out that will be captured by $Reader.ReadLine(). But in some cases there wont be any output to get captured for the reader in that case the reader needs to look data from the stream and if there is no data the ReadLine() gets stuck/blocked waiting for the data from the console stream, whereas we need the ReadLine() to just wait for 5 seconds. If there is no data, it needs to get timed out and move on to the next command.
Please let me know whether there is any way to timeout $Reader.ReadLine() in PowerShell?
I see that in Java/C# we can use $Reader.ReadLine(1000) to timeout after 1 second but that doesn't seem to be working on PowerShell.
$tcpConnection = New-Object System.Net.Sockets.TcpClient($Computername, $Port)
$tcpStream = $tcpConnection.GetStream()
$reader = New-Object System.IO.StreamReader($tcpStream)
$writer = New-Object System.IO.StreamWriter($tcpStream)
$writer.AutoFlush = $true
$buffer = New-Object System.Byte[] 1024
$encoding = New-Object System.Text.AsciiEncoding
while ($tcpStream.DataAvailable) {
$reader.ReadLine()
}
if ($tcpConnection.Connected) {
$writer.WriteLine($username)
$reader.ReadLine()
$writer.WriteLine($password)
$reader.ReadLine()
try {
# if there is any data it will show here if there is no data then
# it should get timed out after 5 seconds
$Reader.ReadLine()
} catch {
Write-Host "Login Failed"
}
}
I would say that you should read this post C# Stream.Read with timeout
Converting that to your code sample, should end up with something like this.
$tcpConnection = New-Object System.Net.Sockets.TcpClient($Computername, $Port)
#This is one way you could try
$tcpConnection.ReceiveTimeout = 5000;
$tcpStream = $tcpConnection.GetStream()
$reader = New-Object System.IO.StreamReader($tcpStream)
$writer = New-Object System.IO.StreamWriter($tcpStream)
$writer.AutoFlush = $true
$buffer = New-Object System.Byte[] 1024
$encoding = New-Object System.Text.AsciiEncoding
while ($tcpStream.DataAvailable) {
$reader.ReadLine()
}
if ($tcpConnection.Connected) {
$writer.WriteLine($username)
$reader.ReadLine()
$writer.WriteLine($password)
$reader.ReadLine()
try {
# if there is any data it will show here if there is no data then
# it should get timed out after 5 seconds
$Reader.ReadLine()
} catch {
Write-Host "Login Failed"
}
}
Take it for a spin and let me know if it works or not.
Updated:
Updated to reflect the code to only contain the working solution.

ProgressBar shows on ISE but not on Console. Powershell responsive GUI on Runspace

I've been working on a simple proof of concept / template of a script in which I have the default Runspace to run heavy tasks and another one to run a responsive GUI.
I have tested various methods to be able to communicate the runspaces. At first I tried the Control.Invoke but some opinions on different forums and a weird behaviour on tests led me to use a message based intercomunication based on a Synchronized HashTable. ProgressBar works with control.Invoke, but, executing some other actions, like disabling multiple buttons on a form performs very slow.
1st problem: I would like to show a progressbar (marquee) when the task is executing, changing its visible state. However, the progressbar is showed when the script runs on ISE, but not when it does on console. I think it is because main runspace is busy, but I don't understand how it could affect the GUI runspace...
2nd: on the script I post below, I'm using scriptblocks through a stack variable to pass the commands between runspaces. Then, each runspace (main runspace does it through pooling and GUI through a timer) checks if a task is pending of execution in the stack, and, if so, executes it. However if I would want to call a function declared on the other runspace (Test-OutOfMainScopeUiFunction in this example), I couldn't. I would get a runtime error on the scriptblock. I have thought solutions to this like:
-Importing functions on both runspaces
-Making functions global or use functon delegates¿?
-Passing a string with the commands to execute in spite of a scriptblock -> Using this one at the moment, but don't like very much... error prone
Any solution to the progress bar problem or script improvements will be appreciated. Thanks!
$global:x = [Hashtable]::Synchronized(#{})
$global:x.MainDispatcher = new-object system.collections.stack
$global:x.UiDispatcher = new-object system.collections.stack
$global:x.GuiExited = $false
$formSB = {
[reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
[reflection.assembly]::LoadWithPartialName("System.Drawing")
Function Test-OutOfMainScopeUiFunction
{
$x.Host.Ui.WriteLine("Test-OutOfMainScopeUiFunction Executed")
}
Function Execute-OnMainRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.Host.Ui.WriteLine("`r`nAdding task")
$x.MainDispatcher.Push($ScriptBlock)
$x.Host.Ui.WriteLine("Task added")
$x.Host.Ui.WriteLine("Task: $($x.MainDispatcher)")
}
$form = New-Object System.Windows.Forms.Form
$button = New-Object System.Windows.Forms.Button
$button.Text = 'click'
$button.Dock = [System.Windows.Forms.DockStyle]::Right
$progressBar = (New-Object -TypeName System.Windows.Forms.ProgressBar)
$ProgressBar.Style = [System.Windows.Forms.ProgressBarStyle]::Marquee
$ProgressBar.MarqueeAnimationSpeed = 50
$ProgressBar.Dock = [System.Windows.Forms.DockStyle]::Bottom
$ProgressBar.Visible = $false
$label = New-Object System.Windows.Forms.Label
$label.Text = 'ready'
$label.Dock = [System.Windows.Forms.DockStyle]::Top
$timer=New-Object System.Windows.Forms.Timer
$timer.Interval=100
$timer.add_Tick({
if($x.UiDispatcher.Count -gt 0)
{
& $($x.UiDispatcher.Pop())
}
})
$form.Controls.add($label)
$form.Controls.add($button)
$form.Controls.add($progressBar)
Add-Member -InputObject $form -Name Label -Value $label -MemberType NoteProperty
Add-Member -InputObject $form -Name ProgressBar -Value $progressBar -MemberType NoteProperty
$button.add_click({
Execute-OnMainRs -ScriptBlock {
write-host "MainRS: Long Task pushed from the UI started on: $(Get-Date)"
start-sleep -s 2
write-host "MainRS: Long Task pushed from the UI finished on: $(Get-Date)"
}
})
$form.add_closed({ $x.GuiExited = $true })
$x.Form = $form
$timer.Start()
$form.ShowDialog()
}
Function Execute-OnRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.Host = $Host
$rs = [RunspaceFactory]::CreateRunspace()
$rs.ApartmentState,$rs.ThreadOptions = "STA","ReUseThread"
$rs.Open()
$rs.SessionStateProxy.SetVariable("x",$x)
$ps = [PowerShell]::Create().AddScript($ScriptBlock)
$ps.Runspace = $rs
$handle = $ps.BeginInvoke()
#Almacenar variables del RS
$x.Handle = $handle
$x.Ps = $ps
}
Function Execute-OnUiRs
{
param(
[Parameter(Mandatory=$true)]
[ScriptBlock]$ScriptBlock
)
$x.UiDispatcher.Push($ScriptBlock)
}
Function Dispatch-PendingJobs
{
while($global:x.GuiExited -eq $false) {
if($global:x.MainDispatcher.Count -gt 0)
{
Execute-OnUiRs -ScriptBlock {
$msg = "UIRS: MainThread informs: Long Task started on $(Get-Date)."
$global:x.Form.Label.Text = $msg
$global:x.Form.ProgressBar.Visible = $true
$x.host.ui.WriteLine($msg)
#Next line throws an error visible on UI runspace error stream
Test-OutOfMainScopeUiFunction
}
& $($global:x.MainDispatcher.Pop())
Execute-OnUiRs -ScriptBlock {
$msg = "UIRS: MainThread informs: Long Task finished on $(Get-Date)."
$global:x.Form.Label.Text = $msg
$global:x.Form.ProgressBar.Visible = $false
$x.host.ui.WriteLine($msg)
}
write-host "UI Streams: $($global:x.Ps.Streams |out-string)"
}
else
{
start-sleep -m 100
}
}
}
Found the solution... http://community.idera.com/powershell/powertips/b/tips/posts/enabling-visual-styles
VisualStyles must be enabled first. The problem is not related with runspaces stuff. This is a brief and clearer code example taken from Power Shell marquee progress bar not working with the fix.
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$window = New-Object Windows.Forms.Form
$window.Size = New-Object Drawing.Size #(400,75)
$window.StartPosition = "CenterScreen"
$window.Font = New-Object System.Drawing.Font("Calibri",11,[System.Drawing.FontStyle]::Bold)
$window.Text = "STARTING UP"
$ProgressBar1 = New-Object System.Windows.Forms.ProgressBar
$ProgressBar1.Location = New-Object System.Drawing.Point(10, 10)
$ProgressBar1.Size = New-Object System.Drawing.Size(365, 20)
$ProgressBar1.Style = "Marquee"
$ProgressBar1.MarqueeAnimationSpeed = 20
$window.Controls.Add($ProgressBar1)
$window.ShowDialog()

Populating a datagridview

I ma working on a form that will search all connected drives for PST files.
I can get it working with the following command:-
Get-PSDrive -PSProvider "filesystem"|%{get-childitem $_.root -include *.pst -r}|select name, directoryname, #{name="Size (GB)";expression ={"{0:N2}" -f ($_.length/1GB)}}
The only problem is it takes about 45 minutes to run through all the drives and finish the search. I was thinking of trying to speed it up by using the windows search index.
I have got this....
function Searchindex{
$query="SELECT System.ItemName, system.ItemPathDisplay, System.ItemTypeText, System.Size FROM SystemIndex where system.itemtypetext = 'outlook data file'"
$objConnection = New-Object -ComObject adodb.connection
$objrecordset = New-Object -ComObject adodb.recordset
$objconnection.open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';")
$objrecordset.open($query, $objConnection)
$array=#()
Try { $objrecordset.MoveFirst() }
Catch [system.exception] { "no records returned" }
do
{
Write-host ($objrecordset.Fields.Item("System.ItemName")).value `
($objrecordset.Fields.Item("System.ItemPathDisplay")).value `
($objrecordset.Fields.Item("System.ITemTypeText")).value `
($objrecordset.Fields.Item("System.Size")).value
if(-not($objrecordset.EOF)) {$objrecordset.MoveNext()}
} Until ($objrecordset.EOF)
$objrecordset.Close()
$objConnection.Close()
$objrecordset = $null
$objConnection = $null
[gc]::collect()
}
this outputs the details to the screen in a few seconds which is perfect but I can't work out how to display it in a datagrid view.
I am using primal form to create the forms.
Once the data is populated in the datagridview I want to be able to select records and copy them to a new location
Can anyone help?
TIA
Andy
I'm not familiar with DataGridView but I feel if you had an object you would be better capable to manipulate it.
function Searchindex{
$query="SELECT System.ItemName, system.ItemPathDisplay, System.ItemTypeText, System.Size FROM SystemIndex where system.itemtypetext = 'outlook data file'"
$objConnection = New-Object -ComObject adodb.connection
$objrecordset = New-Object -ComObject adodb.recordset
$objconnection.open("Provider=Search.CollatorDSO;Extended Properties='Application=Windows';")
$objrecordset.open($query, $objConnection)
$array=#()
Try { $objrecordset.MoveFirst() }
Catch [system.exception] { "no records returned" }
do
{
$array += [pscustomobject]#{
Name = ($objrecordset.Fields.Item("System.ItemName")).value
Path = ($objrecordset.Fields.Item("System.ItemPathDisplay")).value
TypeText = ($objrecordset.Fields.Item("System.ITemTypeText")).value
Size = ($objrecordset.Fields.Item("System.Size")).value
}
If(-not($objrecordset.EOF)) {$objrecordset.MoveNext()}
} Until ($objrecordset.EOF)
$objrecordset.Close()
$objConnection.Close()
$objrecordset = $null
$objConnection = $null
[gc]::collect()
$array
}
This will send out a custom PowerShell object array. You already had the variable $array initialized. We just needed to populate it.
Then you could use something like this to filter out the files you are looking for.
Searchindex | Out-GridView -PassThru
After hitting Ok it will only output the records selected.
DataGridView
with multiselect and return
$global:results = #()
#...searchindex function is here ....
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Size=New-Object System.Drawing.Size(800,400)
$dataGridView.SelectionMode = 'FullRowSelect'
$dataGridView.MultiSelect = $true
$go = New-Object System.Windows.Forms.Button
$go.Location = New-Object System.Drawing.Size(300,450)
$go.Size = New-Object System.Drawing.Size(75,23)
$go.text = "Select"
$form.Controls.Add($go)
$form.Controls.Add($dataGridView)
$arraylist = New-Object System.Collections.ArrayList
$arraylist.AddRange((Searchindex))
$dataGridView.DataSource = $arraylist
$dataGridView.Columns[0].width = 240
$go.Add_Click(
{
$dataGridView.SelectedRows| ForEach-Object{
$global:results += [pscustomobject]#{
Name = $dataGridView.Rows[$_.Index].Cells[0].Value
Path = $dataGridView.Rows[$_.Index].Cells[1].Value
TypeText = $dataGridView.Rows[$_.Index].Cells[2].Value
Size = $dataGridView.Rows[$_.Index].Cells[3].Value
}
$form.Close()
}
})
$form.ShowDialog()
$global:results
There is a lot to cover here but look at the examples and let me know how this works for you. It will return all selected rows back as objects in the global variable $global:results. It needs to be global as output does not persist outside the $go.Add_Click. The searchindex function is there but omitted in the second code sample to save space.