What's wrong with my powershell script? :( - database

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()
}

Related

Need Help Understanding Runspaces with 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

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

Terminated Users script - adapt single-run script to run for multiple users?

I don't want to run script for each terminated user, I assume I need to use do while loop for this so the it will continually loop over and over again until the ESC option.
I have been trying to do this in powershell, but I am new to it and am still learning. Do you have any idea?
$wshell = New-Object -ComObject Wscript.Shell
$wshell.Popup("Please enter in your Domain Admin credentials. Please remember it should be in the form of DOMAIN\username.",0,"Credentials Needed!",0x0)
$creds = Get-Credential
$PSDefaultParameterValues = #{"*-AD*:Credential"=$creds}
#Here we create the connection to the exchange server. Edit with your mailserver info
$ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://**editmemithyourwebmailservername**/PowerShell
Import-PSSession $ExchangeSession
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Terminated Employee Process Form"
$objForm.Size = New-Object System.Drawing.Size(500,400)
$objForm.StartPosition = "CenterScreen"
$objForm.MaximizeBox = $False
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter")
{$userinput=$UserTextBox.Text;$forwardemail=$ForwardingTextBox.Text;$ticketnumber=$TicketTextBox.Text;$disableuser=$DisableUserCheckbox.Checked;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape")
{$objForm.Close()}})
$Font = New-Object System.Drawing.Font("Verdana",8,[System.Drawing.FontStyle]::Bold)
#$objForm.Font = $Font
#VERSION NUMBER
$VersionLabel = New-Object System.Windows.Forms.Label
$VersionLabel.Location = New-Object System.Drawing.Size(450,10)
$VersionLabel.Size = New-Object System.Drawing.Size(120,20)
$VersionLabel.Font = $Font
$VersionLabel.Text = "V1"
$objForm.Controls.Add($VersionLabel)
#OK AND CANCEL BUTTONS
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,320)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$userinput=$UserTextBox.Text;$ticketnumber=$TicketTextBox.Text;$forwardemail=$ForwardingTextBox.Text;$disableuser=$DisableUserCheckbox.Checked;$objForm.Close()})
$objForm.Controls.Add($OKButton)
#USERNAME LABEL
$UserLabel = New-Object System.Windows.Forms.Label
$UserLabel.Location = New-Object System.Drawing.Size(10,20)
$UserLabel.Size = New-Object System.Drawing.Size(280,20)
$UserLabel.Text = "Username of Terminated Employee"
$objForm.Controls.Add($UserLabel)
#USERNAME TEXT BOX
$UserTextBox = New-Object System.Windows.Forms.TextBox
$UserTextBox.Location = New-Object System.Drawing.Size(10,40)
$UserTextBox.Size = New-Object System.Drawing.Size(180,20)
$objForm.Controls.Add($UserTextBox)
#DISABLE USER CHECKBOX CONTROL
$DisableUserCheckbox = New-Object System.Windows.Forms.Checkbox
$DisableUserCheckbox.Location = New-Object System.Drawing.Size(220,30)
$DisableUserCheckbox.Size = New-Object System.Drawing.Size(120,40)
$DisableUserCheckbox.Text = "Disable The User?"
$objForm.Controls.Add($DisableUserCheckbox)
#FORWARD EMAIL LABEL
$FowardEmailLabel = New-Object System.Windows.Forms.Label
$FowardEmailLabel.Location = New-Object System.Drawing.Size(10,80)
$FowardEmailLabel.Size = New-Object System.Drawing.Size(280,20)
$FowardEmailLabel.Text = "Forward Email to Manager? If Yes, Type In Email Address"
$objForm.Controls.Add($FowardEmailLabel)
#FORWARD EMAIL TEXT BOX
$ForwardingTextBox = New-Object System.Windows.Forms.TextBox
$ForwardingTextBox.Location = New-Object System.Drawing.Size(10,100)
$ForwardingTextBox.Size = New-Object System.Drawing.Size(180,40)
$objForm.Controls.Add($ForwardingTextBox)
#ENTER TICKET NUMBER TEXT LABEL
$TicketLabel = New-Object System.Windows.Forms.Label
$TicketLabel.Location = New-Object System.Drawing.Size(10,150)
$TicketLabel.Size = New-Object System.Drawing.Size(80,20)
$TicketLabel.Text = "Issue Number"
$objForm.Controls.Add($TicketLabel)
$TicketTextBox = New-Object System.Windows.Forms.TextBox
$TicketTextBox.Location = New-Object System.Drawing.Size(10,170)
$TicketTextBox.Size = New-Object System.Drawing.Size(40,250)
$objForm.Controls.Add($TicketTextBox)
#CANCEL BUTTONS
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(350,320)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$objForm.Close(); $cancel = $true})
$objForm.Controls.Add($CancelButton)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
if ($cancel) {return}
#$OKButton.Add_Click({$userinput=$UserTextBox.Text;$ticketnumber=$TicketTextBox.Text;$forwardemail=$ForwardingTextBox.Text;$disableuser=$DisableUserCheckbox.Checked;$objForm.Close()})
#$CancelButton.Add_Click({$objForm.Close()})
#COMMON GLOBAL VARIABLES
$disableusercheckbox=$DisableUserCheckbox.Checked
$userinput=$UserTextBox.Text
$forwardemail=$ForwardingTextBox.Text
$ticketnumber=$TicketTextBox.Text
$Month = Get-Date -format MM
$Day = Get-Date -format dd
$Year = Get-Date -format yyyy
If ($OKButton.Add_Click) {
########
#ACTIVE DIRECTORY ACTIONS
#########
#DISABLE THE USER
If ($disableusercheckbox -eq $true)
{
Disable-ADAccount -Identity $userinput
$disabled = $userinput + " has been disabled"
} else {
$notdisabled = $userinput + " has not been disabled at this time"
}
#GETS ALL GROUPS USER WAS PART OF BEFORE BLOWING THEM OUT
$User = $userinput
$List=#()
$Groups = Get-ADUser -Identity $User -Properties * | select -ExpandProperty memberof
foreach($i in $Groups){
$i = ($i -split ',')[0]
$List += "`r`n" + ($i -creplace 'CN=|}','')
}
#BLOW OUT GROUPS OF USER EXCEPT DOMAIN USERS
(get-aduser $userinput -properties memberof).memberof|remove-adgroupmember -member $userinput -Confirm:$False
#SETS THE USERS TITLE,COMPANY/MANAGER TO DISABLED
set-aduser -identity $userinput -title "CompanyName - Disabled $Month/$Day/$Year"
set-aduser -identity $userinput -company $null
set-aduser -identity $userinput -manager $null
set-aduser -identity $userinput -department $null
set-aduser -identity $userinput -description "CompanyName - Disabled $Month/$Day/$Year per Issue# $ticketnumber"
#CHANGES THE USERS PASSWORD
$newpwd = ConvertTo-SecureString -String "G00dBye#1234" -AsPlainText –Force
Set-ADAccountPassword $userinput –NewPassword $newpwd -Reset
#MOVES THE USER TO DISABLED USERS
Get-ADUser -Filter { samAccountName -like $userinput } | Move-ADObject –TargetPath "OU=Disabled Users,OU=User Accounts,DC=domain,DC=com"
#HIDES USER FROM GLOBAL ADDRESS BOOK and configures forwarding
Set-Mailbox -Identity $userinput -ForwardingAddress $forwardemail -HiddenFromAddressListsEnabled $true
#REMOVES THE SESSION
Remove-PSsession $ExchangeSession
Start-Sleep -s 2
}
Form text boxes are going to be tricky to handle multiple users. So instead, use a CSV file as input file with the username and ticketnumber as headers.
$CSVList = Import-csv C:\Temp\InputFile.csv
Use a Foreach loop to loop through each line in the CSV.
Foreach ($Line.Username in $CSVList)
{
#your termination code goes here
#if at any point you need to use the ticket number for this user
$TicketNo = $Line.TicketNumber
#This is the Header you used in the csv file.
}
For Display, create an object table (much like a csv file) and show it using a Datagridview or the more simpler Out-Gridview instead of labels so you can support displaying multiple users. (Out-Gridview will create its own window though)

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