Need Help Understanding Runspaces with WPF - wpf

I'm new to any sort of programming at all and I've been working on this tool, but I've finally hit a wall. I've read every article, post, and watched every video I could find but runspaces just aren't making sense to me. All I'm trying to do is get the auto search function to run on separate thread to keep the main window from locking up. I've been stuck on this for so long now that I considered ditching Powershell to start learning Python, but I'd rather understand this than quit. I think I even understand how to set the function to run on a separate runspace but the specific part that I don't understand is how to run the UI in it's own runspace and have the two communicate. I've even tried using some boilerplate with a syncHash table but I could never get it working. I'd really appreciate any help before I go insane.
All Files:
https://mega.nz/folder/251yHaBJ#bHYNYdNfAmia5mEhE5IOKQ
Powershell script in question:
#Xaml import
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$xamlFile = "MainWindow.xaml"
$inputXAML = Get-Content -Path $xamlFile -Raw
$inputXAML = $inputXAML -replace 'mc:Ignorable="d"','' -replace "x:N","N" -replace '^<Win.*','<Window'
[XML]$XAML = $inputXAML
$reader = New-Object System.Xml.XmlNodeReader $XAML
try {
$psform = [Windows.Markup.XamlReader]::Load($reader)
}
catch {
Write-Host $_.Exception
throw
}
$xaml.SelectNodes("//*[#Name]") | ForEach-Object {
try {
Set-Variable -Name "var_$($_.Name)" -Value $psform.FindName($_.Name) -ErrorAction Stop
}
catch {
throw
}
}
#Functions
function autoSearch {
$Drives = Get-PSDrive -PSProvider "FileSystem"
$a1 = foreach ($Drive in $Drives) {Get-ChildItem -Path $Drive.Root -Recurse -ErrorAction SilentlyContinue -Directory -Filter "Steam"}
$a2 = Get-ChildItem (Join-Path $a1.FullName "userdata") -ErrorAction SilentlyContinue -Directory
$a3 = Get-ChildItem (Join-Path $a2.FullName "*") -ErrorAction SilentlyContinue -Directory -Filter "1446780"
if ($a3) {
$var_saveR.Foreground = "Green"
$var_saveR.Content = "Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 3000
}
$timer.add_Tick({$var_saveR.Content = ""})
$var_loc1.Content = $a3
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $a3.FullName + "\*"
$fileContent | Set-Content $filePath -Force
}
else {
$var_saveR.Foreground = "Red"
$var_saveR.Content = "Not Found"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 7000
}
$timer.add_Tick({$var_saveR.Content = ""})
}
}
function manualSave {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc1.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "1"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc1.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function manualBack {
Add-Type -AssemblyName System.Windows.Forms
$browser = New-Object System.Windows.Forms.FolderBrowserDialog
$browser.ShowDialog()
$var_loc2.Content = $browser.SelectedPath
$filePath = "Config.txt"
$lineNumber = "2"
$fileContent = Get-Content $filePath
$fileContent[$lineNumber-1] = $var_loc2.Content + "\*"
$fileContent | Set-Content $filePath -Force
}
function backup {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[0] ($a1[1] -replace '\\\*','') -Recurse
$var_backupStatus.Foreground = "Green"
$var_backupStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_backupStatus.Content = ""})
}
function restore {
$file = "Config.txt"
$a1 = Get-Content $file
$a1[0]
$a1[1]
Copy-Item $a1[1] ($a1[0] -replace '\\\*','') -Recurse #-Confirm
$var_restoreStatus.Foreground = "Green"
$var_restoreStatus.Content = "Done"
$timer = New-Object System.Windows.Forms.Timer -Property #{
Enabled = $true
Interval = 5000
}
$timer.add_Tick({$var_restoreStatus.Content = ""})
}
#UI
$var_aSearchButton.Add_Click({autoSearch})
$var_mSetButton1.Add_Click({manualSave})
$var_mSetButton2.Add_Click({manualBack})
$var_backupButton.Add_Click({backup})
$var_restoreButton.Add_Click({restore})
$var_loc1.Content = (Get-Content "Config.txt" -TotalCount 1) -replace '\\\*',''
$var_loc2.Content = (Get-Content "Config.txt" -TotalCount 2)[-1] -replace '\\\*',''
$psform.ShowDialog() | Out-Null

Related

Powershell GUI auto generate buttons with functions

TLDR:
How can I make a generated variable, and then call that variable later within a Add_click.
I am sure some kind of serialization of each Object/button I make is what is needed.
I am building a small tool that reads from a csv to create a button, and function.
the csv looks something like
Name Type Link Script
Powershell App C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Empty
FixXYZ Fix Empty -ScriptStuffHere-
The tool will then make a button with the Name, (work in progress to filter apps and fixes), and when you click the button, if its an app will do start ($link) and if its a fix it will run that script.
My issue is I have it making the button and giving them names, and the name of the button stays, but the function does not.
full code:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
#=======================================================
$Form = New-Object system.Windows.Forms.Form
$Form.text = "Form"
$Form.TopMost = $false
$Form.ClientSize = New-Object System.Drawing.Point(760,400)
$Form.minimumSize = New-Object System.Drawing.Size(760,400)
$Form.maximumSize = New-Object System.Drawing.Size(760,400)
$GetCSV = import-csv "C:\File.csv"
$Details = $GetCSV.Name
$DeviceList = $GetCSV
$Count = $DeviceList.Lines.Count
$ObjectNumber = -1
Write-Host "Total Entries:" $Count
$x = 0 #up down
$z = 0 #left right
$Names = #($DeviceList.Lines)
$Names | ForEach-Object{
$ObjectNumber += 1
Write-Host "Object:" $ObjectNumber
$x += 0
$z += 120
if($z -eq 720){
$x += 120
$z = 0
Write-Host "New Row"}
Write-Host "x" $x
Write-Host "z" $z
$ButtonLabel = ($GetCSV[$ObjectNumber]).Name
set-Variable -Name "var$ObjectNumber" -Value ($GetCSV[$ObjectNumber] | Select Name, Type, Link, Script, File, FileSource)
Write-Host "Name: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Name
Write-Host "Type: " (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Type
Write-Host "Link: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link
Write-Host "Script: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).Script
Write-Host "File: "(Get-Variable -Name "var$ObjectNumber" -ValueOnly).File
Write-Host =========================
$_ = New-Object system.Windows.Forms.Button
$_.text = $ButtonLabel
$_.width = 100
$_.height = 100
$_.location = New-Object System.Drawing.Point($z,$x)
$_.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$_.Add_Click({ Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})
$Form.Controls.Add($_)
}
[void]$Form.ShowDialog()
I am very certain my issue is coming from
$_.Add_Click({Start (Get-Variable -Name "var$ObjectNumber" -ValueOnly).Link})
I know the issue is with $ObjectNumber because that number is getting +1 each time the ForEach is gone through, so when I click a button, its taking "var$OjbectNumber" as its Last number. Clicking the button works, but all buttons open the last entries link.
The answer was using a unused property to throw my desired call back variable in.
So in this case, i have a folder with with programs, the button will be made, and set the $Button.Text (its name) as the name of the .exe, and then it sets the $Button.Tag as the file path, so when I go do the button.Add_Click , I just call the Button.Tag as it will have the path of my Exe.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '580,400'
$Form.Text = "Test"
$Form.TopMost = $false
$Form.FormBorderStyle = 'Fixed3D'
$Form.MaximizeBox = $false
$Form.minimumSize = New-Object System.Drawing.Size(580,400)
$Form.maximumSize = New-Object System.Drawing.Size(580,400)
#Place Holder Form Junk Above
#Reset these on Run
$Global:x = 10 #Reset up down
$Global:z = 10 #Reset left right
$Global:ObjectNumber = -1 #Reset Object Count
Function Make-Button([string] $ToolName, [string] $ToolPath, [string] $SetZ, [string] $SetX){
$Button = New-Object system.Windows.Forms.Button
$Button.text = $ToolName
$Button.width = 120
$Button.height = 120
$Button.location = New-Object System.Drawing.Point($SetZ,$SetX)
$Button.Font = New-Object System.Drawing.Font('Franklin Gothic',10)
$Button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
$Button.FlatAppearance.BorderSize = 0
$Button.ForeColor = [System.Drawing.ColorTranslator]::FromHtml("#ffffff")
$Button.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#515582")
$Button.tag = $ToolPath #<- this is where the answer was. Throwing my desired callback into an unused property of the the Button. in this case, i used _.Tag
$Button.Add_Click{start $this.tag}
$Form.Controls.AddRange(#($Button))
Write-Host "$ToolName"
Write-Host "$ToolPath"
Write-Host "$SetZ"
Write-Host "$SetX"
}
function Get-Position{
switch ($Global:ObjectNumber) {
-1{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber
$Global:x = 0
$Global:z += 0}
Default{$Global:ObjectNumber += 1
Write-Host "Object:" $Global:ObjectNumber
$Global:x += 0
$Global:z += 140}
}#end switch
if($Global:z -eq 570){ #Make New Row
$Global:x += 140
$Global:z = 10
Write-Host "New Row"
}
}
$Tools = Get-ChildItem "C:\WINDOWS\system32" -Filter *.exe
$Count = ( $Tools | Measure-Object ).Count;
Write-Host "Entries:" $Count
$Names = #($Tools) #Put Tools in Array
$Names | ForEach-Object{
Get-Position
Make-Button ($_.Name).replace(".exe","") ($_.FullName) ($z) ($x)
}
#End Form
$Test.Add_Shown( {$Test.Activate()})
$Test.ShowDialog()
[void]$Form.ShowDialog()
Continuing from my comment...
A small refactor to get this to show where things are
Clear-Host
Add-Type -AssemblyName System.Windows.Forms,
PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.text = 'Form'
$Form.TopMost = $false
$Form.ClientSize = New-Object System.Drawing.Point(760,400)
$Form.minimumSize = New-Object System.Drawing.Size(760,400)
$Form.maximumSize = New-Object System.Drawing.Size(760,400)
$GetCSV = Import-Csv -LiteralPath 'D:\Scripts\File.csv'
$Details = $GetCSV.Name
$DeviceList = $GetCSV
$Count = $DeviceList.Count
$ObjectNumber = -1
"Total Entries: $Count`n`n"
$ObjDown = 0
$ObjRight = 0
$DeviceList.Name |
ForEach-Object{
$ObjectNumber += 1
"`nObject: $ObjectNumber"
$x = 0
$ObjRight = 120
if($ObjRight -eq 720)
{
$x = 120
$ObjRight = 0
'New Row'
}
"x $x"
"z $ObjRight"
$ButtonLabel = ($GetCSV[$ObjectNumber]).Name
set-Variable -Name $("var$ObjectNumber") -Value ($GetCSV[$ObjectNumber] |
Select Name, Type, Link, Script, File, FileSource)
("Name: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Name)")
("Type: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Type)")
("Link: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Link)")
("Script: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).Script)")
("File: $((Get-Variable -Name $("var$ObjectNumber") -ValueOnly).File)")
$PSitem = New-Object system.Windows.Forms.Button
$PSitem.text = $ButtonLabel
$PSitem.width = 100
$PSitem.height = 100
$PSitem.location = New-Object System.Drawing.Point($ObjRight,$x)
$PSitem.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$PSitem.Add_Click({
$(Get-Variable -Name $("var$ObjectNumber") -ValueOnly)
})
$Form.Controls.Add($PSitem)
}
#[void]$Form.ShowDialog()
Here is an example I gave as an answer to another post to dynamically create UX/UI elements and assign a form event, though not using an external file, it's the same concept.
How to create multiple button with PowerShell?
Add tooltip and form event, like so...
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(381,316)
$Form.text = "Auto Button UI"
$Form.TopMost = $false
$Form.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")
$i = 0
Get-Variable -Name 'Button*' |
Remove-Variable
$objTooltip = New-Object System.Windows.Forms.ToolTip
$objTooltip.InitialDelay = 100
1..3 |
foreach{
$CurrentButton = $null
$CurrentButton = New-Object System.Windows.Forms.Button
$CurrentButton.Location = "$(50+100*$i), 275"
$CurrentButton.Text = $PSItem
$CurrentButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
New-Variable "Button$PSitem" $CurrentButton
$objTooltip.SetToolTip(
$CurrentButton,
"Execute action assigned to $($CurrentButton.Text)"
)
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($CurrentButton.Text)", $($CurrentButton.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
$i++
$form.Controls.Add($CurrentButton)
}
[void]$Form.ShowDialog()
Yet, though it adds the event to each button element, the message text is the last one passed. Unless explicitly called as in the example from the link.
To adapt the second example in the answer already provided here so that the message text is not just the last one passed, you can change the reference within the event to the instance this.text rather than the iteratively updated $CurrentButton.text
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
Credit to jrv https://social.technet.microsoft.com/Forums/ie/en-US/09ff4141-6222-4bff-b8a9-a1253e0d378a/powershell-form-procedurally-creating-buttons?forum=ITCG
Full code with serialization of button object and event:
Clear-Host
Add-Type -AssemblyName System.Windows.Forms,
PresentationFramework
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(381,316)
$Form.text = "Auto Button UI"
$Form.TopMost = $false
$Form.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#c9f6fe")
$i = 0
Get-Variable -Name 'Button*' |
Remove-Variable
$objTooltip = New-Object System.Windows.Forms.ToolTip
$objTooltip.InitialDelay = 100
1..3 |
foreach{
$CurrentButton = $null
$CurrentButton = New-Object System.Windows.Forms.Button
$CurrentButton.Location = "$(50+100*$i), 275"
$CurrentButton.Text = $PSitem
$CurrentButton.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
New-Variable "Button$PSitem" $CurrentButton
$objTooltip.SetToolTip(
$CurrentButton,
"Execute action assigned to $($CurrentButton.Text)"
)
$CurrentButton.add_click(
{
[System.Windows.Forms.MessageBox]::
Show(
"$($this.Text)", $($this.Text), [System.Windows.Forms.MessageBoxButtons]::
OKCancel, [System.Windows.Forms.MessageBoxIcon]::Information
)
})
$i++
$form.Controls.Add($CurrentButton)
}
[void]$Form.ShowDialog()

How to close form GUI after checking existing file in PowerShell?

I want to check an existing file, if the process still waiting for the file, it will display a GUI window. After the file is exist, the window will close automatically.
I tried this code, the window can not close, even the file already exist.
Checking the file:
$SN = "708TSTA"
$MAC = "2E5961370"
function Find {
$n = 0
while (-not (Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})) {
Start-Sleep -s 1
D:\Auto\GUI.ps1
$n++
(Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"})
Write-Host "Attempt no $n"
}
Write-Host ">>Flag found after $n attempts"
return $true
}
if (Find) {
Write-Host "Found"
}
GUI.ps1:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Check = Get-ChildItem -Name "D:\SERVER\" | Where-Object {$_ -like "*$SN-$MAC*"}
if($Check) {
Write-Host "File Exist"
$Form.Close()
}
$Form.Controls.AddRange(#($Label1,$Label2))
[void]$Form.ShowDialog()
Instead of doing Start-Sleep inside the GUI, it is better to use a timer so the form stays responsive.
I changed the code of the GUI.ps1 (not the way it looks) like this:
Param (
[string]$Path = '*.*',
[string]$MaxAttempts = 5
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# set things up for the timer
$script:nAttempts = 0
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # 1 second
$timer.Add_Tick({
$global:Result = $null
$script:nAttempts++
$file = Get-Item -Path $Path
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
elseif ($script:nAttempts -ge $MaxAttempts) {
$global:Result = [PSCustomObject]#{
Exists = $false
FileName = ''
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
})
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = '578,400'
$Form.Text = "Form"
$Form.BackColor = "#c1daf7"
$Form.WindowState = 'Maximized'
$Form.FormBorderStyle = "FixedDialog"
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$Form.Controls.AddRange(#($Label1,$Label2))
# start the timer as soon as the dialog is visible
$Form.Add_Shown({ $timer.Start() })
[void]$Form.ShowDialog()
# clean up when done
$Form.Dispose()
And to call it from your other script, use:
$SN = "708TSTA"
$MAC = "2E5961370"
function Test-FileExists {
$file = Get-Item -Path "D:\*$SN-$MAC*"
if ($file) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Attempts = 1
}
}
else {
& "D:\GUI.ps1" -Path "D:\*$SN-$MAC*" -MaxAttempts 3
}
}
# call the function that can call the GUI.ps1 script
Test-FileExists
# check the Global result object
if ($global:Result.Exists) {
Write-Host "File '$($global:Result.FileName)' Exists. Found after $($global:Result.Attempts) attempts." -ForegroundColor Green
}
else {
Write-Host "File not found after $($global:Result.Attempts) attempts." -ForegroundColor Red
}
Update
As per your comments, I understand that the calling script should show the form (which does nothing more that show on screen) AND is responsible for closing it after the file has been found.
The code below should do what you ask by defining the $Form as a global variable and by using the .Show() method of the form instead of ShowDialog():
GUI.ps1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$global:Form = New-Object System.Windows.Forms.Form
$global:Form.ClientSize = '578,400'
$global:Form.Text = "Form"
$global:Form.BackColor = "#c1daf7"
$global:Form.WindowState = 'Maximized'
$global:Form.FormBorderStyle = "FixedDialog"
$global:Form.ControlBox = $false # hide sizing and close buttons
$global:Form.TopMost = $true
$Label1 = New-Object System.Windows.Forms.Label
$Label1.Text = "UNDER PROCESS"
$Label1.AutoSize = $true
$Label1.Width = 25
$Label1.Height = 10
$Label1.Location = New-Object System.Drawing.Point(600,300)
$Label1.Font = 'Microsoft Sans Serif,30,style=Bold,Underline'
$Label1.ForeColor = "#d0021b"
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "WAITING"
$Label2.AutoSize = $true
$Label2.Width = 25
$Label2.Height = 10
$Label2.Location = New-Object System.Drawing.Point(770,500)
$Label2.Font = 'Microsoft Sans Serif,20,style=Bold'
$Label2.ForeColor = "#fb0505"
$global:Form.Controls.AddRange(#($Label1,$Label2))
# don't use ShowDialog() here because it will block the calling script
$global:Form.Show()
the calling script
function Test-FileExists {
[CmdletBinding()]
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
[string]$Path,
[string]$Pattern = '*.*'
)
$nAttempts = 1
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
if (!$file) {
# show the GUI
& "D:\GUI.ps1"
do {
Start-Sleep -Seconds 1
$nAttempts++
Write-Verbose "Attempt No. $nAttempts"
$file = Get-ChildItem -Path $Path -Filter $Pattern -File | Select-Object -First 1
} until ($file)
# clean up the form
$global:Form.Dispose()
$global:Form = $null
}
Write-Verbose "File '$($file.FullName)' Exists. Found after $nAttempts attempt(s)."
return $true
}
$SN = "708TSTA"
$MAC = "2E5961370"
# call the function that can call the GUI.ps1 script
if (Test-FileExists -Path 'D:\SERVER\SHARE' -Pattern "*$SN-$MAC*" -Verbose) {
Write-Host "Found"
}
Hope that helps

What's wrong with my powershell script? :(

I'm trying to make an automated script that automatically directs to a
secure website in IE, enters login credentials, and goes to a database tab
and a database link to download. This will require no user interaction and
be initiated through windows task scheduler.
However, it works about 75% of the time. I am new to
powershell and made novice mistakes, so any help or direction would
greatly be appreciated. Thank you!
PowerShell.exe -windowstyle hidden {
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
Write-Output "$(Get-TimeStamp) Script Executed $dc" | Out-file C:\Users
\JohnSmith\Desktop\Script\ScriptLog.txt -append
$username = "admin"
$password = "admin123"
$ie = new-object -com InternetExplorer.Application
#Navigate to the login page
$ie.navigate("Login Page")
#Wait for the page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$ie.visible = $true #comment this line after debugging
#Assigning DOM to $doc variable
$doc = $ie.document
try {
$usernameField = $doc.getElementById('userName')
#write-host $usernameField
$usernameField.value = $username
write-host $username
$passwordField = $doc.getElementById('password')
$passwordField.value = $password
write-host $pass
#Find and click the submit button
$submitButton = $doc.getElementById('login')
write-host $submitButton
$submitButton.click()
#Wait until login is complete
do {sleep 1} until (-not ($ie.Busy))
} catch {$null}
do {sleep 1} until (-not ($ie.Busy))
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$ie = new-object -com internetexplorer.application
$ie.visible=$true
$ie.navigate('Database Download' )
while($ie.busy) {sleep 1}
$link = $ie.Document.getElementsByTagName('A') | where-object
{$_.innerText -eq 'Download the Complete Database'}
$link.click()
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("{TAB}");
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like '*Internet Explorer*'
} | ForEach-Object
{
$_.Quit()
[Runtime.Interopservices.Marshal]::ReleaseComObject($_)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
Here was my finished script. It's not as elegant as I would like it to be, but it does what I need it to do, for now! Thank you!
PowerShell.exe -windowstyle hidden {
function Get-TimeStamp {
return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
}
#Creates appending log file for everytime script runs
Write-Output "$(Get-TimeStamp) Script Executed $dc" | Out-file C:\Users
\JohnSmith\Desktop\DatabaseScript\DatabaseScriptLog.txt -append
$username = "admin"
$password = "admin123"
#write-host $pass
#Create the IE com object
$ie = new-object -com InternetExplorer.Application
#Navigate to the login page
Start-Process 'https://www.LoginPageToTheSecureSite'
<#$ie.navigate("https://www.LoginPageToTheSecureSite")
#Wait for the page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$ie.visible = $true
#Assign the DOM to the $doc variable
$doc = $ie.document
try {
#Find the username field and set the value to that of variable
$usernameField = $doc.getElementById('User ID')
#write-host $usernameField
$usernameField.value = $username
write-host $username
#Find the password field and set the value to that of the result
#of a call to the get-password function with the parameter defined at
top
$passwordField = $doc.getElementById('Password')
$passwordField.value = $password
write-host $pass
#Submit button
$submitButton = $doc.getElementById('Login')
write-host $submitButton
$submitButton.click()
#Wait until login is complete
do {sleep 1} until (-not ($ie.Busy))
} catch {$null}
#Wait for page to finish loading
do {sleep 1} until (-not ($ie.Busy))
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('title of the application window')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
Start-Process 'https://www.DownloadTheDatabaseSelectionTab'
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
Start-Process 'https://www.DownloadThisAsZipFile.zip'
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Opening Database Zip File')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait('~');
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate('Internet Explorer')
Sleep 1
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.SendKeys]::SendWait("{TAB 2}");
#Closes every instance of IE
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like '*Internet Explorer*'
} | ForEach-Object {
$_.Quit()
}
#Releases COM Object, cleans up.
(New-Object -COM 'Shell.Application').Windows() | Where-Object {
$_.Name -like 'Internet Explorer'
} | ForEach-Object {
$_.Quit()
[Runtime.Interopservices.Marshal]::ReleaseComObject($_)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}

Array removed after function?

I am making a script that goes into all servers we're hosting and gets all members of a specific group and the domain name, and then exports it to a file. I'm saving the users and the domain names into two arrays AA (user array) and DA (domain array) AA stands for användararray, and "användare" is users in swedish so it makes sense to me.
I noticed that the export step didn't work, no users or domain names were exported, so I tried to print them in the function. But it doesn't print anything, so I tried to print it in a different location (didn't work). After some experimenting I came to the conlusion that the only place the arrays actually contains any information is inside the foreach loop where I save the users that I find??!
Here is the code
unction GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
#$DA + ";" + $Anvandare.Name
$DA + ";" + $AA
}
}
$output
}
}
$DA
$AA
}
function Export {
Write-Host("C")
$filsökväg = "C:\Users\322sien\Desktop\Coolkids.csv"
$ColForetag = "Företag"
$ColAnvandare = "Användare"
$Emptyline = "`n"
$delimiter = ";"
for ($p = 1; $p -le $DomainArray.Length; $p++) {
$ColForetag + $delimiter + $ColAnvandare | Out-File $filsökväg
$DA + $delimiter + $AA | Out-File $filsökväg -Append
}
}
ReadInfo
GetData
Export
Can anyone help me with this? I've sat down with this all day and i cant find a solution.
Your variables $DA and $AA are bound to GetData function, so they live only there. You could make them available inside your script by changing it's scope.
Change this:
$AA = #($Anvandare.Name)
$DA = gc env:UserDomain
To this:
$script:AA = #($Anvandare.Name)
$script:DA = gc env:UserDomain
So they will now be available for other functions inside the script.
Also I found the ways to improve your script, hope you can see the logic:
function GetData([int]$p) {
Write-Host("B")
for ($row = 1; $row -le $UsernamesArray.Length; $row++)
{
if($CloudArray[$row] -eq 1)
{
.
$secstr = New-Object -TypeName System.Security.SecureString
$PasswordsArray[$row].ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $UsernamesArray[$row], $secstr
[array]$output = Invoke-Command -computername $AddressArray[$row] -credential $cred -ScriptBlock {
Import-Module Activedirectory
$array = #()
foreach ($Anvandare in (Get-ADGroupMember fjärrskrivbordsanvändare))
{
$object = New-Object PSObject
$object | Add-Member -MemberType NoteProperty -Name AA -Value #($Anvandare.Name)
$object | Add-Member -MemberType NoteProperty -Name DA -Value (gc env:UserDomain)
$object | Add-Member -MemberType NoteProperty -Name Something -Value $DA + ";" + $AA
$array += $object
}
Write-Output $array
}
Write-Output $output
}
}
}
Your function will now output some data.

Check if file is in use before exporting

I'm writing a script that will export an excel file to a PDF. I got that part working, However, because I'm saving on top of an existing PDF it cannot be open when the export happens. I'm looking for a way to have PowerShell check if the file is currently open, and if so, wait for X seconds and then check again. If not it can proceed.
It currently works perfectly and breaks if the PDF is open, however I need it to loop.
Here's what I have so far:
$path = "c:\users\XXXXX\documents"
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$excelFiles = Get-ChildItem -Path $path -Include spreadsheet.xlsx -Recurse
$File = "c:\users\XXXXX\documents\Exported.pdf"
try {
[IO.File]::OpenWrite($File).Close();
$true
} catch {
break
}
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
foreach ($wb in $excelFiles) {
$filepath = Join-Path -Path $path -ChildPath ($wb.BaseName + ".pdf")
$workbook = $objExcel.Workbooks.Open($wb.FullName, 3)
$workbook.Saved = $true
"saving $filepath"
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath)
$objExcel.Workbooks.Close()
}
$objExcel.Quit()
This should do what you want; thanks to Ben Baird for the Test-FileLock function.
function Test-FileLock {
param ([parameter(Mandatory=$true)][string]$Path)
$oFile = New-Object System.IO.FileInfo $Path
if ((Test-Path -Path $Path) -eq $false)
{
return $false
}
try
{
$oStream = $oFile.Open([System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
if ($oStream)
{
$oStream.Close()
}
# file is unlocked.
$false
}
catch
{
# file is locked by a process.
return $true
}
}
$path = "c:\users\XXXXX\documents"
$xlFixedFormat = "Microsoft.Office.Interop.Excel.xlFixedFormatType" -as [type]
$excelFiles = Get-ChildItem -Path $path -Include spreadsheet.xlsx -Recurse
$File = "c:\users\XXXXX\documents\Exported.pdf"
while((Test-FileLock $file) -eq $true)
{
Start-Sleep -Seconds 3
}
$objExcel = New-Object -ComObject Excel.Application
$objExcel.Visible = $false
foreach ($wb in $excelFiles) {
$filepath = Join-Path -Path $path -ChildPath ($wb.BaseName + ".pdf")
$workbook = $objExcel.Workbooks.Open($wb.FullName, 3)
$workbook.Saved = $true
"saving $filepath"
$workbook.ExportAsFixedFormat($xlFixedFormat::xlTypePDF, $filepath)
$objExcel.Workbooks.Close()
}
$objExcel.Quit()
The code will check for a file lock and if it is detected wait 3 seconds and then try again. Once the lock is cleared the PDF export code will run.

Resources