I am trying to write a Powershell script that will interact with elements of a webpage. To learn the concepts for the script, I am using Armorgames.com as the target url with my aim to use powershell to log into the site, then make a search of available games according to specified search terms, then export the results to a CSV. The attached code sample is what I currently have. But I'm having trouble understanding which parts of the webpage elements I'm searching for and how to input values into the available fields.
I am also getting the error "Method invocation failed because [mshtml.HTMLDocumentClass] does not contain a method named 'getElementsById'" and I don't understand why
$ie = New-Object -ComObject "InternetExplorer.Application"
$requestUrl = "https://armorgames.com/"
$userId = "username-input";
$passwordId = "password-input"
$signIn = "act"
$ie.visible = $true
#ie.silent = $true
$ie.navigate($requestUrl)
while ($ie.busy) {
Start-Sleep -Milliseconds 100
}
$doc = $ie.Document
$doc.getElementsById("input") | % {
if ($_.id -ne $null){
if ($_.id.Contains($signIn)) { $btn = $_ }
if ($_.id.Contains($passwordId)) { $pwd = $_ }
if ($_.id.Contains($userId)) { $user = $_ }
}
}
$user.value = "ExampleUser"
$pwd.value = "MyPassord"
$btn.disabled = $false
$btn.click()
Write-Output $ie
Write-Output "doc is $doc"
Related
function Enforce-MFA($exclude){
Connect-MsolService
$excludedUsers = 'admin','admin2','admin3','admin4' + $exclude
$excluded = ($excludedUsers | ForEach-Object { [regex]::Escape($_) }) -join '|'
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enforced"
$sta = #($st)
$array = (Get-MsolUser | Where-Object { $_.DisplayName -notmatch $excluded }).UserPrincipalName
ForEach ($user in $array)
{
Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $sta
Write-Host "Complete"
}
}
The general function is to grab a list of objects, exclude certain objects, and Enforce MFA for the remaining objects. This script seemed to work without any issue last week, but this week, I'm getting no data from the Array variable. I was working on a lot of different changes and I'm thinking I may have messed something up in the process, but I'm just not seeing it. What did I mess up or what am I not seeing?
You forgot to add -Credential $cred to the Connect-MsolService.
You should create that connection first and take it out of the function.
function Enforce-MFA {
$excludeTheseUsers = 'admin', 'user1', 'user2' # etc.
# for using the regex `-notmatch` operator later, you need to combine the entries with the regex OR sign ('|'),
# but you need to make sure to escape special characters some names may contain
$excludes = ($excludeTheseUsers | ForEach-Object { [regex]::Escape($_) }) -join '|'
# create the StrongAuthenticationRequirement object just once, to use on all users
$st = New-Object -TypeName Microsoft.Online.Administration.StrongAuthenticationRequirement
$st.RelyingParty = "*"
$st.State = "Enabled"
$sta = #($st)
# get an array of UserPrincipalNames
$array = (Get-MsolUser | Where-Object { $_.DisplayName -notmatch $excludes }).UserPrincipalName
foreach ($user in $array) {
Set-MsolUser -UserPrincipalName $user -StrongAuthenticationRequirements $sta
}
Write-Host "Enforcing MFA Complete"
}
# ask for credentials to make the connection
$cred = Get-Credential -Message 'Please enter your credentials to connect to Azure Active Directory'
Connect-MsolService -Credential $cred
As for your loop, try something like this:
# enter an endless loop
while($true) {
$var = Read-Host -Prompt "Enter the corresponding number: 1: Enforce 2: Enable 3: Disable 4: Exit"
switch($var){
1 { Enforce-MFA }
2 { Enable-MFA }
3 { Disable-MFA }
4 { exit }
default{ "Please choose either 1, 2 ,3 or 4" }
}
}
The text is an html file. The line I am interested in looks something like:
<td>INC1234</td><td>INC1235</td><td>INC1236</td>
The INC numbers are different from file to file. I'd like to parse through the line by saying something like:
if like <td>INC, then concatenate '<td><a href="https://www.website.com/=' + INC# + '>"
To give an output like:
<td><a href="https://www.website.com/=INC1234>INC1234</a></td><td><a href="https://www.website.com/=INC1235>INC1235</a></td><td><a href="https://www.website.com/=INC1236>INC1236</a></td>"
EDIT1: Ok, if I do something like:
$parse = (-split (Get-Content -Raw C:\Temp\report.txt) -match '<td>INC')
$parse
It will find the characters, but it will return the entire line rather than looking for more that match the 'INC'. Presumably because they all reside on the same line with no spaces.
EDIT2: Maybe this will help. What I'm doing is using PowerShell to write SQL commands, send it to our SQL Server, return the data and use PSWriteHTML to build the report which works fantastic. But I am wanting the first column, which is the ticket number (eg. INC1234) to be a link to the ticket it reads.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]$ReportName
)
## Build the query box
function Read-MultiLineInputBoxDialog([string]$Message, [string]$WindowTitle, [string]$DefaultText)
{
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
## Create the Label.
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(10,10)
$label.Size = New-Object System.Drawing.Size(280,20)
$label.AutoSize = $true
$label.Text = $Message
## Create the TextBox used to capture the user's text.
$textBox = New-Object System.Windows.Forms.TextBox
$textBox.Location = New-Object System.Drawing.Size(10,40)
$textBox.Size = New-Object System.Drawing.Size(575,200)
$textBox.AcceptsReturn = $true
$textBox.AcceptsTab = $false
$textBox.Multiline = $true
$textBox.ScrollBars = 'Both'
$textBox.Text = $DefaultText
## Create the OK button.
$okButton = New-Object System.Windows.Forms.Button
$okButton.Location = New-Object System.Drawing.Size(415,250)
$okButton.Size = New-Object System.Drawing.Size(75,25)
$okButton.Text = "OK"
$okButton.Add_Click({ $form.Tag = $textBox.Text; $form.Close() })
## Create the Cancel button.
$cancelButton = New-Object System.Windows.Forms.Button
$cancelButton.Location = New-Object System.Drawing.Size(510,250)
$cancelButton.Size = New-Object System.Drawing.Size(75,25)
$cancelButton.Text = "Cancel"
$cancelButton.Add_Click({ $form.Tag = $null; $form.Close() })
## Create the form.
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size(610,320)
$form.FormBorderStyle = 'FixedSingle'
$form.StartPosition = "CenterScreen"
$form.AutoSizeMode = 'GrowAndShrink'
$form.Topmost = $True
$form.AcceptButton = $okButton
$form.CancelButton = $cancelButton
$form.ShowInTaskbar = $true
## Add all of the controls to the form.
$form.Controls.Add($label)
$form.Controls.Add($textBox)
$form.Controls.Add($okButton)
$form.Controls.Add($cancelButton)
## Initialize and show the form.
$form.Add_Shown({$form.Activate()})
$form.ShowDialog() > $null # Trash the text of the button that was clicked.
## Return the text that the user entered.
return $form.Tag
}
## Prompt the SQL Query Box
$Query = Read-MultiLineInputBoxDialog -Message "Enter SQL Query Here" -WindowTitle "SQL Query" -DefaultText "SELECT FROM"
if ($Query -eq $null) { Break }
else { Write-Host "You entered the following text: $Query" }
## Pass query to SQL Server
$Pass = Invoke-Sqlcmd -Query $Query -ServerInstance "MY-SERVER-INSTANCE" -Username "USERNAME" -Password "PASSWORD"
## Output the report and save to the network under the specified name
New-HTML {
New-HTMLTable -EnableColumnReorder -DisableInfo -DataTable $Pass -ExcludeProperty "RowError", "RowState", "Table", `
"ItemArray", "HasErrors" -HideFooter -PagingLength 25 -SearchBuilder
New-HTMLTableStyle -FontFamily Calibri -FontSize 15 -FontStyle normal -TextAlign center -TextColor "#0a0a0a"
New-HTMLTableStyle -FontFamily Calibri -BackgroundColor "#fffdb5" -FontSize 15px -TextColor "#0a0a0a" -TextAlign center -Type RowHover
} -ShowHTML -FilePath "\\Server\$ReportName.html" -Online
The report looks something like:
Ticket: Description:
----------------------------
INC1234 Broken Monitor
INC1235 No Sound
The HTML that PSWriteHTML builds throws all of the ticket numbers on one line so I would like to edit that HTML with the <a href=""> tag to dynamically create links for each ticket # mainly because I don't know how to do it in PS and can't seem to find a good answer through Google - which is why I came here.
Although not familiar with PsWriteHtml, I guess you could simply change the Ticket properties in the array you receive in $Pass:
## Pass query to SQL Server
$Pass = Invoke-Sqlcmd -Query $Query -ServerInstance "MY-SERVER-INSTANCE" -Username "USERNAME" -Password "PASSWORD"
# convert all Tickets into hyperlinks
foreach ($item in $Pass) {
$item.Ticket = '<a href="https://www.website.com/={0}>{0}</a>'-f $item.Ticket
}
Then the rest of your code
## Output the report and save to the network under the specified name
New-HTML {...}
I want to run a defined PowerShell function on an add_click event in a Windows form in that script.
I've found lots of examples of how to call a .ps1 script from a click, but not how to call a function.
Use powershell command in add_click
So, here is my script in full. I am curious on two ways of outputting this information.
• Just open a PowerShell console and show the results in there (the pause will prevent it from closing), or
• In some kind of MessageBox (some more Windows Forms magic or techniques or tooltips, or whatever anyone thinks works well - such techniques are all very interesting to me).
function sysx {
$System = get-wmiobject -class "Win32_ComputerSystem"
$Mem = [math]::Ceiling($System.TotalPhysicalMemory / 1024 / 1024 / 1024)
$wmi = gwmi -class Win32_OperatingSystem -computer "."
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
$s = "" ; if ($uptime.Days -ne 1) {$s = "s"}
$uptime_string = "$($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min $($uptime.seconds) sec"
"$Mem GB"
"Up: $uptime_string"
pause
}
# Load Assemblies
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName PresentationFramework # For MessageBox
$MyIcon = [Drawing.Icon]::ExtractAssociatedIcon((Get-Command powershell).Path)
# Create Primary form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Visible = $false
$objForm.WindowState = "minimized"
$objForm.ShowInTaskbar = $false
$objForm.add_Closing({ $objForm.ShowInTaskBar = $False })
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objNotifyIcon.Icon = $MyIcon
$objNotifyIcon.Text = "TrayUtility"
$objNotifyIcon.Visible = $true
$objContextMenu = New-Object System.Windows.Forms.ContextMenu
$ToggleMenuItemPS = New-Object System.Windows.Forms.MenuItem
$ToggleMenuItemPS.Index = 1
$ToggleMenuItemPS.Text = "New PowerShell"
if (Test-Administrator) { $ToggleMenuItemPS.Text = "New PowerShell (Admin)" }
$ToggleMenuItemPS.add_Click({
Start-Process PowerShell.exe
})
$ToggleMenuItemFunction = New-Object System.Windows.Forms.MenuItem
$ToggleMenuItemFunction.Index = 2
$ToggleMenuItemFunction.Text = "Mem and Uptime"
$ToggleMenuItemFunction.add_Click({
# some way to call the 'sysx' function in a PowerShell console, MessageBox, tooltip, etc...
})
# Create an Exit Menu Item
$ExitMenuItem = New-Object System.Windows.Forms.MenuItem
$ExitMenuItem.Index = 5
$ExitMenuItem.Text = "E&xit"
$ExitMenuItem.add_Click({
$objForm.Close()
$objNotifyIcon.visible = $false
})
# Add the Menu Items to the Context Menu
$objContextMenu.MenuItems.Add($ToggleMenuItemPS) | Out-Null
$objContextMenu.MenuItems.Add($ToggleMenuItemFunction) | Out-Null
$objContextMenu.MenuItems.Add($ExitMenuItem) | Out-Null
# Assign the Context Menu
$objNotifyIcon.ContextMenu = $objContextMenu
$objForm.ContextMenu = $objContextMenu
# Show the Form - Keep it open
$objForm.ShowDialog() | Out-Null
$objForm.Dispose()
You can just use only sysx there:
$ToggleMenuItemFunction.add_Click({sysx})
To show results in a message box tweak sysx a bit:
$wshshell = new-object -comobject Wscript.shell
$wsh.PopUp("$($Mem) GB`nUp: $($uptime_string)")
All code has scope, even for output. Your function will run, but since you are note telling the output where to go. I added the Test admin function since it was left out of your post.
I tested and validated the below and it works on my system, in a running PowerShell console/ISE/VSCode session. Meaning, it prints this output to the console (of course you can change that to send to a Message Box, et al) and start a new console as admin.
#region Begin code-behind logic
#
function Test-Administrator
{
([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::
GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}
function Get-Sysx {
$System = get-wmiobject -class "Win32_ComputerSystem"
$Mem = [math]::Ceiling($System.TotalPhysicalMemory / 1024 / 1024 / 1024)
$wmi = gwmi -class Win32_OperatingSystem -computer "."
$LBTime = $wmi.ConvertToDateTime($wmi.Lastbootuptime)
[TimeSpan]$uptime = New-TimeSpan $LBTime $(get-date)
$s = ""
if ($uptime.Days -ne 1) {$s = "s"}
$uptime_string = "$($uptime.days) day$s $($uptime.hours) hr $($uptime.minutes) min $($uptime.seconds) sec"
"$Mem GB"
"Up: $uptime_string"
pause
}
#
#endregion End code behind logic
#region Begin GUI logic
#
# Load Assemblies
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName PresentationFramework # For MessageBox
$MyIcon = [Drawing.Icon]::ExtractAssociatedIcon((Get-Command powershell).Path)
# Create Primary form
$objForm = New-Object System.Windows.Forms.Form
$objForm.Visible = $false
$objForm.WindowState = "minimized"
$objForm.ShowInTaskbar = $false
$objForm.add_Closing({ $objForm.ShowInTaskBar = $False })
$objNotifyIcon = New-Object System.Windows.Forms.NotifyIcon
$objNotifyIcon.Icon = $MyIcon
$objNotifyIcon.Text = "TrayUtility"
$objNotifyIcon.Visible = $true
$objContextMenu = New-Object System.Windows.Forms.ContextMenu
$ToggleMenuItemPS = New-Object System.Windows.Forms.MenuItem
$ToggleMenuItemPS.Index = 1
$ToggleMenuItemPS.Text = "New PowerShell"
if (Test-Administrator) { $ToggleMenuItemPS.Text = "New PowerShell (Admin)" }
$ToggleMenuItemPS.add_Click({
Start-Process PowerShell.exe
})
$ToggleMenuItemFunction = New-Object System.Windows.Forms.MenuItem
$ToggleMenuItemFunction.Index = 2
$ToggleMenuItemFunction.Text = "Mem and Uptime"
$ToggleMenuItemFunction.add_Click({
Get-Sysx | Out-Host
})
# Create an Exit Menu Item
$ExitMenuItem = New-Object System.Windows.Forms.MenuItem
$ExitMenuItem.Index = 5
$ExitMenuItem.Text = "E&xit"
$ExitMenuItem.add_Click({
$objForm.Close()
$objNotifyIcon.visible = $false
})
# Add the Menu Items to the Context Menu
[void]$objContextMenu.MenuItems.Add($ToggleMenuItemPS)
[void]$objContextMenu.MenuItems.Add($ToggleMenuItemFunction)
[void]$objContextMenu.MenuItems.Add($ExitMenuItem)
# Assign the Context Menu
$objNotifyIcon.ContextMenu = $objContextMenu
$objForm.ContextMenu = $objContextMenu
#endregaion End GUI logic
# Show the Form - Keep it open
[void]$objForm.ShowDialog()
$objForm.Dispose()
As for this... [I was hopeful that would work, but using Get-Sysx on its own does nothing.], if you are saying you are not in a PowerShell session when you call this, then that is because you did not start one before the call, so, nothing to write to. So, call a message box instead and the separate call to powershell.exe, and that pause is not needed since the messagebox will stay shown until dismissed.
$ToggleMenuItemFunction = New-Object System.Windows.Forms.MenuItem
$ToggleMenuItemFunction.Index = 2
$ToggleMenuItemFunction.Text = "Mem and Uptime"
$ToggleMenuItemFunction.add_Click({
[System.Windows.Forms.MessageBox]::Show($(Get-Sysx), 'Information', 0)
})
I have a long list of simple jobs I would like to somewhat automate. It's simple stuff, grab or post info via API and build some reports, nothing fancy.
I decided to build a master script which directs out to a variety of other scripts, each handling its own job. Each one of those little scripts, reference functions from a Utility script which I built that has functions which are common to all the other simple job scripts.
Each of the scripts work perfectly when I run them directly, however, when I try to run them via the master script, which routes to them, they all fail.
One example is that in many cases I need to fetch data from an API but get capped at 1000 object returns when I need 10k+. To solve this, I built a function which recursively calls itself until there is no more data left to collect. Again, this works when called by itself but not from the master script, for some reason, it bails out after the first run (should run 10+ times in this case). Then, it returns nothing.
I am thinking maybe this has something to do with how I am scoping the functions/variables?? Not sure. I have tried scoping to Global, Local & Script but none seem to work. Here's some of the code...
*Master Director Script runs script based on user input*
...
&$choice_hash[$action].script_path
$ScriptDirectory = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
. "$ScriptDirectory\Utilities.psm1"
$user_data = $null
$env_choice = $null
$csv_output_path = $null
$collated_user_data = [System.Collections.ArrayList]#()
function selectEnv {
$global:env_choice = Read-Host #"
> Select an Environment: [Prod] or [Dev]
Your Choice
"#
if ($env_choice -ne 'Prod' -and $env_choice -ne 'Dev') {
consoleCmt $env_choice
consoleCmt 'Invalid Choice. Try again...'
selectEnv
} else {
if ($env_choice -eq 'Prod') {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
} else {
$global:csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
}
$global:user_data = process_data $env_choice 'api/xm/1/people?embed=roles&limit=1000'
}
}
function processUsersData {
foreach($user in $user_data) {
$user_roles = ''
$role_divider = ','
for($i = 0; $i -lt $user.roles.data.length; $i++) {
# Only append a comma if there are more, otherwise leave blank for CSV deliniation
if ($i -eq $user.roles.data.length - 1) {
$role_divider = ''
}
$user_roles += $user.roles.data[$i].name + $role_divider
}
# Build ordered hash table with above data
$sanatized_user = [pscustomobject][ordered]#{id = $user.targetName; firstName = $user.firstName; lastName = $user.lastName; siteName = $user.site.name; roles = $user_roles }
# Shovel into storage array used for building the CSV
$global:collated_user_data += $sanatized_user
}
}
notice 'Initiating Groups Report Script'
selectEnv
processUsersData
exportCsv $collated_user_data $csv_output_path
Utility Script (relevant functions being called)
$res = $null
$content = #()
...
function process_data($env, $url) {
fetch_data $env $url
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
return $content **Should return full collection of data, but fails after one pass**
}
function fetch_data($env, $url) {
$base = generateEnvBase $env
$path = "$base/$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$global:res = ConvertFrom-Json $req
}
function fetch_more($env, $url) {
$base = generateEnvBase $env
$path = "$base$url"
$req = Invoke-WebRequest -Credential $cred -Uri $path -Method GET
$res = ConvertFrom-Json $req
foreach($i in $res.data) {
$global:content += $i
}
if($res.links.next) {
fetch_more $env $res.links.next
}
}
Sorry in advance if I have not followed procedure or etiquette, I'm new here.
This should work if you declare all variables in Main.ps1 that are needed by functions. You could also use the "Script" scope when creating a new variable inside a function that you want to use outside the function. Example $Script:Var = "Stuff" created inside a function will be available to whole script.
Directory Structure
C:\Script\Root
| Main.ps1
\---Utilities
fetch_data.ps1
fetch_more.ps1
processUsersData.ps1
process_data.ps1
selectEnv.ps1
Main.ps1
#---[ Initization ]---#
# Strings
[String]$RootPath = $PSScriptRoot
[String]$UtilPath = "$($RootPath)\Utilities"
[String]$env_choice = $null
[String]$csv_output_path = $null
# Arrays
[Array]$user_data = #()
[Array]$content = #()
[Array]$collated_user_data = #()
[Array]$res = #()
#---[ Source in Utilities ]---#
# Get the scripts
$Utilities = Get-ChildItem -Path "$UtilPath" -File | Where-Object {$_.Extension -eq ".ps1"}
# Source in each one
foreach ($Item in $Utilities) {
.$Item.FullName
}
#---[ Select an Environment ]---#
# Get the User's choice
$env_choice = selectEnv
# Process the choice
switch ($env_choice) {
Prod {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Prod.csv'
$user_data = process_data 'Prod' 'api/xm/1/people?embed=roles&limit=1000'
}
Dev {
$csv_output_path = '\\etoprod\******\Exports\Report_Users_Dev.csv'
$user_data = process_data 'Dev' 'api/xm/1/people?embed=roles&limit=1000'
}
Test {
Write-Output "Test is not an option. Choose wisely."
exit 1
}
Default {
Write-Output "Unknown Environment Choice."
exit 1
}
}
#---[ Process Users and Export ]---#
processUsersData
exportCsv $collated_user_data $csv_output_path
selectEnv.ps1
function selectEnv {
$Title = "Environment:"
$Info = "Please choose an environment"
# Options
$Prod = New-Object System.Management.Automation.Host.ChoiceDescription '&Prod', 'Production environment'
$Dev = New-Object System.Management.Automation.Host.ChoiceDescription '&Dev', 'Development environment'
$Test = New-Object System.Management.Automation.Host.ChoiceDescription '&Test', 'Testing environment'
$Options = [System.Management.Automation.Host.ChoiceDescription[]]($Prod, $Dev, $Test)
$Default = 0
# Promp the User
$Choice = $host.UI.PromptForChoice($Title , $Info , $Options, $Default)
$Result = $Options[$Choice].Label -Replace '&',''
return $Result
}
I'm aware of a simple pop-up function for PowerShell, e.g.:
function popUp($text,$title) {
$a = new-object -comobject wscript.shell
$b = $a.popup($text,0,$title,0)
}
popUp "Enter your demographics" "Demographics"
But I am unable to find an equivalent for getting a pop-up to ask for input.
Sure, there is Read-Line, but it prompts from the console.
And then there is this complex function, which seems overkill for a script that will ask for input once or twice:
function getValues($formTitle, $textTitle){
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = $formTitle
$objForm.Size = New-Object System.Drawing.Size(300,200)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") {$x=$objTextBox.Text;$objForm.Close()}})
$objForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$objForm.Close()}})
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$Script:userInput=$objTextBox.Text;$objForm.Close()})
$objForm.Controls.Add($OKButton)
$CANCELButton = New-Object System.Windows.Forms.Button
$CANCELButton.Location = New-Object System.Drawing.Size(150,120)
$CANCELButton.Size = New-Object System.Drawing.Size(75,23)
$CANCELButton.Text = "CANCEL"
$CANCELButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CANCELButton)
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20)
$objLabel.Size = New-Object System.Drawing.Size(280,30)
$objLabel.Text = $textTitle
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,50)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
return $userInput
}
$schema = getValues "Database Schema" "Enter database schema"
Probably the simplest way is to use the InputBox method of the Microsoft.VisualBasic.Interaction class:
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$title = 'Demographics'
$msg = 'Enter your demographics:'
$text = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
The simplest way to get an input box is with the Read-Host cmdlet and -AsSecureString parameter.
$us = Read-Host 'Enter Your User Name:' -AsSecureString
$pw = Read-Host 'Enter Your Password:' -AsSecureString
This is especially useful if you are gathering login info like my example above. If you prefer to keep the variables obfuscated as SecureString objects you can convert the variables on the fly like this:
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($us))
[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pw))
If the info does not need to be secure at all you can convert it to plain text:
$user = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($us))
Read-Host and -AsSecureString appear to have been included in all PowerShell versions (1-6) but I do not have PowerShell 1 or 2 to ensure the commands work identically.
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/read-host?view=powershell-3.0
It would be something like this
function CustomInputBox([string] $title, [string] $message, [string] $defaultText)
{
$inputObject = new-object -comobject MSScriptControl.ScriptControl
$inputObject.language = "vbscript"
$inputObject.addcode("function getInput() getInput = inputbox(`"$message`",`"$title`" , `"$defaultText`") end function" )
$_userInput = $inputObject.eval("getInput")
return $_userInput
}
Then you can call the function similar to this.
$userInput = CustomInputBox "User Name" "Please enter your name." ""
if ( $userInput -ne $null )
{
echo "Input was [$userInput]"
}
else
{
echo "User cancelled the form!"
}
This is the most simple way to do this that I can think of.