Powershell WinForms UI refresh / live update - multithreading

I'm currently pulling my hair out trying to update a Winforms UI using the 'Register-objectevent' Cmdlet.
What I'm trying to do is get the Register-ObjectEvent to update the label in the form ever tick on the timer.
I've done hours of research on this, and I know it's something to do with multithreading / invoking, but I can't get my head around how to make it work !
If someone could show me / help me to get this script to update the label on the form by the timer, that would be amazing ! I've got lots of Winforms that would benifit from multithreading, but I need to get my head around it first !
Here's the script I'm trying to get working, any help is greatly appreciated :)
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles() | out-null
$form1 = New-Object System.Windows.Forms.Form
$OnLoadForm_StateCorrection=
{
$form1.WindowState = $InitialFormWindowState
}
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 600
$System_Drawing_Size.Width = 1200
$form1.ClientSize = $System_Drawing_Size
$form1.MaximizeBox = $False
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.KeyPreview = $True
$form1.FormBorderStyle = 1
$form1.Name = "form1"
$form1.StartPosition = 1
$form1.backcolor = [System.Drawing.Color]::FromArgb(255,240,240,240)
$timer = New-Object System.Timers.Timer
$timer.Interval = 1000
$timer.AutoReset = $true
$timeout = 0
$num=0
$action = {
$num++
write-host "test"
$vollabel.text=$num
$timer.stop()
}
Register-ObjectEvent -InputObject $timer -SourceIdentifier TimerElapsed -EventName Elapsed -Action $action
$timer.start()
$vollabel = New-Object System.Windows.Forms.Label
$vollabel.Location = "0,0"
$form1.Controls.Add($vollabel)
$InitialFormWindowState = $form1.WindowState
$form1.add_Load($OnLoadForm_StateCorrection)
$form1.Add_Shown({$form1.Activate()})
$form1.ShowDialog()| Out-Null

I pared down your script a bit for a working proof of concept - add back in anything you needed:
#('System.Drawing','System.Windows.Forms') | %{ [reflection.assembly]::LoadWithPartialName($_) | Out-Null }
[System.Windows.Forms.Application]::EnableVisualStyles() | out-null
$form1 = New-Object System.Windows.Forms.Form -Property #{
MaximizeBox = $False
KeyPreview = $True
FormBorderStyle = 1
Name = "form1"
StartPosition = 1
backcolor = [System.Drawing.Color]::FromArgb(255,240,240,240)
ClientSize = New-Object System.Drawing.Size -Property #{Height = 600;Width = 1200}
}
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$timer = New-Object System.Windows.Forms.Timer -Property #{Interval = 1000} #Forms.Timer doesn't support AutoReset property
$script:num=0 #scope must be at script level to increment in event handler
$timer.start()
$timer.add_Tick({
$script:num +=1
write-host "test $script:num"
$vollabel.text=$script:num
})
$vollabel = New-Object System.Windows.Forms.Label -Property #{Location = "0,0"}
$form1.Controls.Add($vollabel)
$form1.ShowDialog()| Out-Null
$timer.stop() #This will keep running in the background unless you stop it
A few notes:
Form.ShowDialog() is blocking and stops the script execution.
System.Windows.Forms.Timer has slightly different properties than System.Timers.Timer and can take a ScriptBlock or a function name as a parameter to add_Tick()
An event handler ScriptBlock has its own scope, but you can share variables with the $ScopeName:VariableName syntax. I couldn't get $num to increment unless I set the scope to $Script

Related

Looping for-each and creating an excel sheet (.xlsx)

I'm trying to create a script that pulls print device from a group of servers housed in a text file. The script works fine except it only pulls one device from one server then the script completes. I'm trying to get this to work then loop in another command to combine all the data from all the sheets and look for dissimilarities between the server(s).
clear-host
# Get list of servers from text file
$sites = Get-Content -Path "User\user$\user\Documents\Working Folder\2132023\test.txt"
$counter = 4
# And here
foreach ($site in $sites) {
$result = Get-Printer -ComputerName $site | Select Name, DriverName, PortName, ShareName
#Create an Excel object
$ExcelObj = New-Object -comobject Excel.Application
$ExcelObj.Visible = $true
# Add a workbook
$ExcelWorkBook = $ExcelObj.Workbooks.Add()
$ExcelWorkSheet = $ExcelWorkBook.Worksheets.Item(1)
# Rename the worksheet
$ExcelWorkSheet.Name = $site
# Fill in the head of the table
$ExcelWorkSheet.Cells.Item(1, 1) = 'Device Name'
$ExcelWorkSheet.Cells.Item(1, 2) = 'Driver Name'
$ExcelWorkSheet.Cells.Item(1, 3) = 'Port Name'
$ExcelWorkSheet.Cells.Item(1, 4) = 'Share Name'
# Make the table head bold, set the font size and the column width
$ExcelWorkSheet.Rows.Item(1).Font.Bold = $true
$ExcelWorkSheet.Rows.Item(1).Font.size = 15
$ExcelWorkSheet.Columns.Item(1).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(2).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(3).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(4).ColumnWidth = 28
# Fill in Excel cells with the data obtained from the server
$ExcelWorkSheet.Columns.Item(1).Rows.Item($counter) = $result.Name
$ExcelWorkSheet.Columns.Item(2).Rows.Item($counter) = $result.DriverName
$ExcelWorkSheet.Columns.Item(3).Rows.Item($counter) = $result.PortName
$ExcelWorkSheet.Columns.Item(4).Rows.Item($counter) = $result.ShareName
$counter++
}
# Save the report and close Excel:
$ExcelWorkBook.SaveAs('\User\User\Documents\Working Folder\2132023\test.xlsx')
$ExcelWorkBook.Close($true)
That is because you are cfeating a new Excel COM object inside the loop.
Put that part above the loop, and inside create a new worksheet for each server and fill the data.
Because Get-Printer may very well return more that one object, you need to loop over the results from that too.
Try
# use full absolute path here
$outFile = 'X:\Somewhere\Documents\Working Folder\2132023\test.xlsx'
if (Test-Path -Path $outFile -PathType Leaf) { Remove-Item -Path $outFile -Force }
# Create an Excel object
$ExcelObj = New-Object -comobject Excel.Application
$ExcelObj.Visible = $true
# Add a workbook
$ExcelWorkBook = $ExcelObj.Workbooks.Add()
# Get list of servers from text file
$sites = Get-Content -Path "X:\Somewhere\Documents\Working Folder\2132023\test.txt"
foreach ($site in $sites) {
$counter = 2
# Add a sheet
$ExcelWorkSheet = $ExcelWorkBook.Sheets.Add()
# make this the the active sheet
$ExcelWorkSheet.Activate()
# Rename the worksheet
$ExcelWorkSheet.Name = $site
# Fill in the head of the table
$ExcelWorkSheet.Cells.Item(1, 1) = 'Device Name'
$ExcelWorkSheet.Cells.Item(1, 2) = 'Driver Name'
$ExcelWorkSheet.Cells.Item(1, 3) = 'Port Name'
$ExcelWorkSheet.Cells.Item(1, 4) = 'Share Name'
# Make the table head bold, set the font size and the column width
$ExcelWorkSheet.Rows.Item(1).Font.Bold = $true
$ExcelWorkSheet.Rows.Item(1).Font.size = 15
$ExcelWorkSheet.Columns.Item(1).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(2).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(3).ColumnWidth = 28
$ExcelWorkSheet.Columns.Item(4).ColumnWidth = 28
# Fill in Excel cells with the data obtained from the server
Get-Printer -ComputerName $site | Select-Object Name, DriverName, PortName, ShareName | ForEach-Object {
$ExcelWorkSheet.Columns.Item(1).Rows.Item($counter) = $_.Name
$ExcelWorkSheet.Columns.Item(2).Rows.Item($counter) = $_.DriverName
$ExcelWorkSheet.Columns.Item(3).Rows.Item($counter) = $_.PortName
$ExcelWorkSheet.Columns.Item(4).Rows.Item($counter) = $_.ShareName
$counter++
}
}
# Save the report and close Excel:
$ExcelWorkBook.SaveAs($outFile)
$ExcelWorkBook.Close($true)
$ExcelObj.Quit()
# Clean up the used COM objects
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkSheet)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelWorkBook)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ExcelObj)
$null = [System.GC]::Collect()
$null = [System.GC]::WaitForPendingFinalizers()
P.S. The code would run faster if you set $ExcelObj.Visible = $false

How can i export Event Logs using powershell

i'm new to ps scripting , i want to export certain data from event logs. I tried following
$Myexcel = New-Object -ComObject excel.application
$Myexcel.visible = $true
$Myworkbook = $Myexcel.workbooks.add()
$Sheet1 = $Myworkbook.worksheets.item(1)
$Sheet1.name = "summary"
$Sheet1.cells.item(1,1) = 'BootDevice'
$events = Get-WinEvent -FilterHashtable #{logname="Microsoft-Windows-Storage-Storport/Health"; id=511}
# get the first event raw XML
$event = [xml]$events[0].ToXml()
# display its content
#$event.Event.EventData.Data
$BootDevice=$event.Event.EventData.Data | Where-Object {$_.name -eq "BootDevice"}
write-output $BootDevice
$Sheet1.cells.item(2,1) = $BootDevice
$Sheet1.Columns.AutoFit()
$Myfile = 'E:\tmp\test.xlsx'
$Myworkbook.Saveas($Myfile)
$Myexcel.displayalerts = $true
But its giving error
Exception from HRESULT: 0x800A03EC
At line:16 char:1
+ $Sheet1.cells.item(2,1) = $BootDevice
And a blank excel is generated.
Any help will be thankfull.

Eurosign gives strange result

I've a problem when I'm assigning currency format to a specific range in a Excel sheet.
The code in powershell I use to set the format is below :
$LC_ws_tab.range("B1 :B5").NumberFormat = "_(€* #.##0,00_);_(€* (#.##0,00);_(€* ""-""??_);_(#_)"
The thing i find strange is that - iff I run the code I see the screenshot below (euro sign = â,-) .
But when I run the code a second time via "run selected code" when excel document is still open there is a euro sign.
Does anyone know why I've this result?
The complete sample code u see below:
function Excel_new {
$object = New-Object -ComObject "Excel.Application"
$object | Add-Member -MemberType ScriptMethod -Name FullQuit -Value {Excel_FullQuit}
$object
}
function Excel_FullQuit {
while ( $this.Workbooks.Count -gt 0 ) {
$this.Workbooks.Item(1).Close()
}
$this.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($this)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()
Stop-Process -processname EXCEL
}
$Excel = Excel_new
$Excel.Visible = $true
$Excel.displayalerts = $false
$path = "C:\somefolder\test.xlsx"
$LC_wb = $Excel.Workbooks.Open($path)
$LC_ws_tab = $LC_wb.Worksheets.Add()
$LC_ws_tab.name = "test"
# $LC_ws_tab.range("A1 :A5").NumberFormat = "#.##0,00 € "
# $LC_ws_tab.range("B1 :B5").NumberFormat = "€ #.##0,00"
# $LC_ws_tab.range("C1 :C5").NumberFormat = "0,00 % "
$LC_ws_tab.Cells.Item( 1 , 1 ) = 49999
$LC_ws_tab.Cells.Item( 1 , 2 ) = 1234.879
$LC_ws_tab.Cells.Item( 1 , 3 ) = 1234.879
$LC_ws_tab.range("B1 :B5").NumberFormat = "_(€* #.##0,00_);_(€* (#.##0,00);_(€* ""-""??_);_(#_)"

Reading value from Excel sheet

$filepath = "C:\Users\Desktop\New folder\Tangent.xlsx"
$sheetname = "sheet"
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
$WorkBook = $objExcel.Workbooks.Open($filepath)
$WorkBook.sheets | Select-Object -Property Name
$WorkSheet = $WorkBook.Sheets.Item($sheetname)
$myObj = [PSCustomObject][ordered]#{
john = $WorkSheet.Range("B1").Text
Rebel = $WorkSheet.Range("B2").Text
MArk = $WorkSheet.Range("B3").Text
Susan = $WorkSheet.Range("B4").Text
Patty = $WorkSheet.Range("B5").Text
}
I have hardcoded all the names into the code which is a weird way of doing it. I want it to read from the Excel directing using command. Can anyone help me please?
Create an empty hashtable and fill it as you iterate over the rows in your Excel sheet, then create the object.
$ht = #{}
$i = 1
while ($WorkSheet.Cells.Item($i, 1).Text) {
$ht[$WorkSheet.Cells.Item($i, 1).Text] = $WorkSheet.Cells.Item($i, 2).Text
$i++
}
$obj = [PSCustomObject]$ht
Untested, as I don't have Excel at hand here.

Need a little help adding a table range from an excel file on sharepoint to an email body with powershell

$if = '\\portal2010.brand.com\sites\HC\2017 count.xlsx'
$excel = New-Object -Com Excel.Application
$Workbook = $Excel.Workbooks.Open($if)
$page = 'HC'
$ws = $Workbook.worksheets | where-object {$_.Name -eq $page}
$range = $ws.Range("V2:AF")
$rows = $range.Rows.Count
$hcTableCopy = $ws.Range("V2:AF$rows").Copy()
$hcTablePaste = $hcTableCopy.PasteSpecial($default, $default, $default, $default, 9, $default, $default)
$SendTo = "e.mail#brand.com"
$SMTPServer = "smtp.brand.com"
$EmailFrom = "e.mail2#brand.com"
$EmailSubject = "Weekly Email"
$Image2 = '\\portal2010.brand.com\sites\HC\HC Dashboards\2017 HC_files\2017 count_25311_image002.png'
$Image4 = '\\portal2010.brand.com\sites\HC\HC Dashboards\2017 HC_files\2017 count_25311_image004.png'
$Image6 = '\\portal2010.brand.com\sites\HC\HC Dashboards\2017 HC_files\2017 count_25311_image006.png'
$Image8 = '\\portal2010.brand.com\sites\HC\HC Dashboards\2017 HC_files\2017 count_25311_image008.png'
$Message = new-object Net.Mail.MailMessage
Add-PSSnapin Microsoft.Exchange.Management.Powershell.Admin -erroraction silentlyContinue
$att2 = new-object Net.Mail.Attachment($Image2)
$att2.ContentId = "att2"
$att4 = new-object Net.Mail.Attachment($Image4)
$att4.ContentId = "att4"
$att6 = new-object Net.Mail.Attachment($Image6)
$att6.ContentId = "att6"
$att8 = new-object Net.Mail.Attachment($Image8)
$att8.ContentId = "att8"
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$body = '
<img src="cid:att2" /><br/><br/>
<img src="cid:att4" /><br/><br/>
<img src="cid:att6" /><br/><br/>
<img src="cid:att8" /><br/><br/>
'
$Message.From = $EmailFrom
$Message.To.Add($SendTo)
$Message.Cc.Add($CCTo1)
$Message.Cc.Add($CCTo2)
$Message.Cc.Add($CCTo3)
$Message.Cc.Add($CCTo4)
$Message.Subject = $EmailSubject
$Message.Body = $body + $hcTablePaste
$Message.IsBodyHTML = $true
$Message.Attachments.Add($att2)
$Message.Attachments.Add($att4)
$Message.Attachments.Add($att6)
$Message.Attachments.Add($att8)
$smtp.Send($Message)
$excel.DisplayAlerts = $False
Start-Sleep -s 5
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel)
The above code is what I have tried so far. I get an error that says it can't open the file from sharepoint at this point and I am not sure the table paste portion of this is going to work right.
I tried modifying the following, but I really don't think I should have to check out and make editable the file just to copy the table range to an email.
foreach ($file in $excelfiles)
{
$workbookpath = $file.fullname
if ($excel.workbooks.canCheckOut($workbookpath)) {
# open the worksheet and check it out
$excelworkbook = $excel.workbooks.Open($workbookpath)
$excelworkbook = $excel.workbooks.CheckOut($workbookpath)
# Don't ask cuz I don't know (yet). You have to open it again.
$excelworkbook = $excel.workbooks.Open($workbookpath)
# Refresh all the pivot tables with the new data.
$excelworkbook.RefreshAll()
# Save and Check it in
$excelworkbook.Save()
$excelworkbook.CheckInWithVersion()
}
}
$excel.quit()
So any help to get the table pasting part would be great. The email send with images just fine other than that.

Resources