I need to compile ps1 to exe. It works fine, but when i've added some windows.forms elements (open file dialog and select dir) it is still compiling but forms are not showing. Then I noticed, that forms are not showing even when I start script with RMB -> Exec with Powershell. But everything works fine in ISE.
Does anybody have similar problem?
Function Get-OpenFile($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "Text files (*.txt)|*.txt"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
$OpenFileDialog.ShowHelp = $true
}
function Select-Folder($message='Folder to save', $path = 0) {
$object = New-Object -comObject Shell.Application
$folder = $object.BrowseForFolder(0, $message, 0, $path)
if ($folder -ne $null) {
$folder.self.Path
}
}
The rest of the code is just parser and renamer.
UPD - Problem is exactly with windows.forms, when I've comment #get-openfile(myDir) , select-folder dialog has been shown. And shows up, after compiling to exe.
ok, nevermind, i just refused open-file dialog.
Related
Here I have a form with a PictureBox in it. I removed borders and now I want to make the background of the form transparent, so when I launch the script we just see the image nothing else.
I am making a splash screen kind of project with an unusual png shape. I tried the "TransperancyKey = Color" thing from .Net but it doesn't work. I want it to run in PowerShell.
# Importing Assembly for Windows Forms
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# Main form/SplashScreen Object
$SScreen = New-Object system.Windows.Forms.Form
$SScreen.BackColor = [System.Drawing.Color]::FromArgb(255,0,0,0)
#$SScreen.BackColor = Color.Lime
$SScreen.StartPosition = 1
$SScreen.FormBorderStyle = 0
$img = [System.Drawing.Image]::Fromfile('./1.png')
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width
$pictureBox.Height = $img.Size.Height
$pictureBox.Image = $img
$SScreen.controls.add($pictureBox)
$SScreen.Width = $pictureBox.Width
$SScreen.Height = $pictureBox.Height
# Open the main form
Start-Process -FilePath "C:\Windows\system32\WindowsPowerShell\v1.0\powershell_ise.exe"
$SScreen.TopMost = $true
$SScreen.Show()
Start-Sleep -seconds 5
$SScreen.Close()```
You can do this without adding a picturebox to the form and simply use the forms own BackgroundImage property for this.
Make sure your image has transparency of course.
For this, I took your image and made all black transparent.
Because I copied from the web page, it won't be as crisp as yours, but it's the idea that counts.
Try:
# Importing Assembly for Windows Forms
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$img = [System.Drawing.Image]::Fromfile('./1.png')
# Main form/SplashScreen Object
$SScreen = New-Object system.Windows.Forms.Form
$SScreen.Width = $img.Width
$SScreen.Height = $img.Height
$SScreen.TopMost = $true
$SScreen.BackgroundImage = $img
$SScreen.AllowTransparency = $true
$SScreen.TransparencyKey = $SScreen.BackColor
$SScreen.StartPosition = 1
$SScreen.FormBorderStyle = 0
# Open the main form
Start-Process -FilePath "C:\Windows\system32\WindowsPowerShell\v1.0\powershell_ise.exe"
$SScreen.Show()
Start-Sleep -Seconds 5
$SScreen.Close()
# tell Windows it can be removed from memory
$SScreen.Dispose()
Result:
I am trying to develop a lite app to run a set of powershell scripts. I have a small user base that will need to run the app. This app will have a few presentable options (scripts) that it can run from the start. The user needs to be able to click a button to run the script.
The issue I'm having is how to reference the file needed when I don't know the specific location that the user will be running the script from.
I need to be able to have a background image, and scripts stored in a small folder that will be included in the app. Is there a way to direct the script to look in the current directory for files?
Here is my code currently:
#Credit: Portions of this code were provided at the following URL:
http://blogs.technet.com/b/stephap/archive/2012/04/23/building-forms-with-powershell-part-1-the-form.aspx#pi47623=2
# LOAD WINDOWS FORMS
# Load the Winforms assembly
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
#Welcome Screen:
#code for welcome splash here. Haven't gotten that far yet.
# CREATE FORM
$Form = New-Object System.Windows.Forms.Form
$Form.Size = New-Object System.Drawing.Size(500,400)
# background. This is where I need help.
$Image = [system.drawing.image]::FromFile("C:\Users\zstewart\Pictures\script runner.png")
#this location is where my question arises. It won't work on another user's machine.
$Form.BackgroundImage = $Image
$Form.BackgroundImageLayout = "Center"
# None, Tile, Center, Stretch, Zoom
#SET FORM TITLE
$form.text = "ScriptRunner v.001"
#TEXT FIELD FOR PRINTING A RESULT
$outputBox = New-Object System.Windows.Forms.TextBox
$outputBox.Location = New-Object System.Drawing.Size(40,60)
$outputBox.Size = New-Object System.Drawing.Size(400,30)
$outputBox.MultiLine = $True
$outputBox.ScrollBars = "Vertical" #had horizontal here. Didn't work.
# BUTTON
# Create Button and set text and location
$button = New-Object Windows.Forms.Button
$button.Size = New-Object Drawing.Point 120,30
$button.text = "Select Script"
$button.Location = New-Object Drawing.Point 170, 100
# INPUT HANDLER BUTTON - ON CLICK IN THIS CASE
# Set up event handler to exit
$button.add_click({
Function Get-FileName($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = "All files (*.*)| *.*"
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.filename
$outputBox.text=$OpenFileDialog.filename
} #end function Get-FileName
Get-FileName -initialDirectory "c:\fso"
})
# Create Button and set text and location
$button2 = New-Object Windows.Forms.Button
$button2.text = "Run Script"
$button2.Size = New-Object Drawing.Point 120,30
$button2.Location = New-Object Drawing.Point 170,140
# INPUT HANDLER RUN BUTTON - RUN SCRIPT
# close button - on click close app
$button2.add_click({
$form.Close() #need to add code to run some included scripts.
})
# ADD CONTROLS TO FORM
$form.controls.add($button)
$form.controls.add($button2)
$Form.Controls.Add($outputBox)
# DISPLAY DIALOG
$form.ShowDialog()
I realize that this is a very VERY dirty code, but I am still trying to learn as I go along. Please excuse my code skills.
In PS v3.0 and above, you can reference the scripts directory with the $PSScriptRoot automatic variable:
$Image = [system.drawing.image]::FromFile("$PSScriptRoot\script runner.png")
To make your script PowerShell 2.0-friendly, you could do the following at the top of your script:
if(-not (Get-Variable -Name 'PSScriptRoot' -Scope 'Script')) {
$Script:PSScriptRoot = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
}
My code:
$ie = new-object -com "InternetExplorer.Application"
$ie.navigate("http://localhost")
$ie.visible = $true
$ie.fullscreen = $true
However, after fullscreen, the window still appears behind the Windows taskbar. When I click the window to give it focus, the taskbar falls behind and it appears how I want it to. How can I do this programmatically? Thank you!
This was a toughy... Not as simple as I thought.
I ended up using a cheat and adding the VB assembly:
Add-Type -Assembly "Microsoft.VisualBasic"
$ie = new-object -com "InternetExplorer.Application"
$ie.navigate("http://localhost")
$ie.visible = $true
$ie.fullscreen = $true
While ($ie.Busy) { Sleep -m 10 }
$ieProc = Get-Process | ? { $_.MainWindowHandle -eq $ie.HWND }
[Microsoft.VisualBasic.Interaction]::AppActivate($ieProc.Id)
I'm trying to create a GUI for one of the scripts I'm running that is really just a fancy way to get input from a user and feed it into a variable that will be used later on in the script. However, when I enter anything into the text input field, it doesn't assign to the variable unless I go to the specific line in the code and run it by itself. What am I doing wrong?
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "File Path"
$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 = "Go for gold!"
$OKButton.Add_Click({$x=$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(100,23)
$CancelButton.Text = "Abort! It's a trap!"
$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,20)
$objLabel.Text = "Please enter the full file path:"
$objForm.Controls.Add($objLabel)
$objTextBox = New-Object System.Windows.Forms.TextBox
$objTextBox.Location = New-Object System.Drawing.Size(10,40)
$objTextBox.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objTextBox)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
$x
I found a solution on here that suggested changing the variable to $global, but that did nothing. Any suggestions are appreciated.
You could always go with a standard VB InputBox:
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic")
[Microsoft.VisualBasic.Interaction]::InputBox("Please enter your name",$MyInvocation.MyCommand.Name)
It's dirty, but it's essentially what you're looking for I think, and it's far fewer lines of code than making your own Windows Form. If you want to hide the console output of loading Microsoft.VisualBasic, then you can do one of two things:
Prepend [System.Reflection.Assembly] with [void], which will tell the console to ignore the output
Pipe it in to Out-Null, and you'll achieve the same effect
One alternative is you can wait for the Windows Form to close (the code should stop until the window opened by $objForm.ShowDialog() has closed, too), and then grab the text out of $objTextBox.Text.
If all you really want is for someone to put in a folder path or file path, though, then this method is by far the easiest to implement and validate:
$vbShell = New-Object -ComObject Shell.Application
$folder = $vbShell.BrowseForFolder(0x00,"Enter your custom text here",0x01)
$path = $folder.Self.Path
Here's the documentation for the Shell object from Microsoft, too, so that you can get the BrowseForFolder method to work the way you want it to:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb774065%28v=vs.85%29.aspx
Hope this helps!
It's a very familiar window... Employed in more Windows applications than I care to count.
I've seen a bunch of closely related posts so I know i'm not alone, but none have given me the answer I'm looking for. Apologies if this has been asked and answered and I couldn't find it.
this script creates a custom notification area balloon, which, if clicked on is meant to open a new IE window to some URL. Works great from the PowerShell ISE GUI that I've been working with it in. Can't get it to work from command-line using any of the options i've seen suggested in other posts. Specifically, can't get the IE window to open. The notification appears no problem, but no IE window...?? Tried with:
powershell . script.ps1
powershell -file script.ps1
command
&
etc.
Thoughts?
My script:
#Load the required assemblies
[void] [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)
#Remove any registered events related to notifications
Remove-Event BalloonClicked_event -ea SilentlyContinue
Unregister-Event -SourceIdentifier BalloonClicked_event -ea silentlycontinue
Remove-Event BalloonClosed_event -ea SilentlyContinue
Unregister-Event -SourceIdentifier BalloonClosed_event -ea silentlycontinue
Remove-Event Disposed -ea SilentlyContinue
Unregister-Event -SourceIdentifier Disposed -ea silentlycontinue
#Create the notification object
$notification = New-Object System.Windows.Forms.NotifyIcon
#Define various parts of the notification
$notification.Icon = [System.Drawing.SystemIcons]::Information
$notification.BalloonTipTitle = “**Reminder**”
$notification.BalloonTipIcon = “Warning”
$title = “message to user”
$notification.BalloonTipText = $title
#Make balloon tip visible when called
$notification.Visible = $True
## Register a click event with action to take based on event
#Balloon message clicked
register-objectevent $notification BalloonTipClicked BalloonClicked_event -Action {
Start-Process 'c:\Program Files\Internet Explorer\iexplore.exe' -ArgumentList 'http://someURL.com' -WindowStyle Maximized -Verb Open
#Get rid of the icon after action is taken
$notification.Dispose()
} | Out-Null
#Balloon message closed
register-objectevent $notification BalloonTipClosed BalloonClosed_event -Action {$notification.Dispose()} | Out-Null
#Call the balloon notification
$notification.ShowBalloonTip(1000)
The reason why it doesn't work in non-interactive prompts is that powershell has already finished processing when the user clicks the balloon.
You can fix that in one of two ways:
add a sleep(1) at the end of the script, so it doesn't end before the user clicking the balloon; (increase the value if needed, although I tested w/ 1 sec just fine)
use the -noexit commandline parameter and then close powershell programatically upon the user clicking the notification or after some delay (I tested that it would launch IE with no changes to your code, didn't bother coding the exit part.)
Changed to C# with help from a real developer. Would appreciate a true powershell answer though if anyone has it.
New code:
$code = #'
using System;
using System.Drawing;
using System.Windows.Forms;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
NotifyIcon nicon = new NotifyIcon();
nicon.BalloonTipIcon = ToolTipIcon.Info;
nicon.BalloonTipText = "message to user";
nicon.BalloonTipTitle = "*** title for user ***";
nicon.Icon = SystemIcons.Information;
nicon.BalloonTipClicked += (sender, e) =>
{
System.Diagnostics.Process.Start("http://website.com");
CleanUp(nicon);
};
nicon.BalloonTipClosed += (sender, e) => CleanUp(nicon);
nicon.Visible = true;
nicon.ShowBalloonTip(1000 * 1);
Application.Run();
}
static void CleanUp(NotifyIcon c)
{
c.Visible = false;
c.Dispose();
Application.Exit();
}
}
'#
Write-Host $code
Add-Type -OutputType WindowsApplication -OutputAssembly c:\temp\test.exe -TypeDefinition $code -ReferencedAssemblies "System.Drawing","System.Windows.Forms" -Language CSharpVersion3
I believe the solution is to start PowerShell.exe (ie. console window) with the -sta parameter.
Windows GUI code needs to run in a thread set as a COM "Single Threaded Apartment" (STA), but by default the worker thread in a PowerShell console is running in a "Multi Threaded Apartment" (MTA).