I do a basic powershell script with a window and a simple button,
On the add_click action i want to execute the "powershell -file $path" command to open another script
in the main the command works, but not when it is in the .add_click({ })
#main
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object Windows.Forms.Form
$btn1 = New-Object Windows.Forms.Button
$btn1.Text = "Button1"
$form.Controls.Add($btn1)
$path = "C:\Users\Administrateur\Desktop\export_vers_test\test_cmd.ps1"
#powershell -file $path #Here it works
$btn1.add_Click({
write-host $path
powershell -file $path #Here it works doesn't works
})
$form.ShowDialog()
Can i have some help please?
You need to pass the string path to your powershell function.
Use paramters for that.
Your function:
function Set-ActionOnClic{
param($path)
write-host $path
}
the call in the click event
$btn1.add_Click({
Set-ActionOnClic -path $path
#Run the script
. $Path
})
Related
There is a script for users to log in, it calls other scripts in turn, depending on the conditions.
In order to call scripts separately manually, the [switch]$Silent parameter has been added. Question - how to pass this parameter inside Start-Job? I tried to add to the list of arguments in different ways - the value always falls into the neighboring parameter, regardless of the order.
Main script example
Param(
[string]$location = 'C:\Users',
[switch]$Silent
)
Start-Job -FilePath ".\Fonts_Install.ps1" -ArgumentList ($Silent,$location) | Wait-Job
Fonts_Install.ps1
Param(
[switch]$Silent = $false,
[string]$location = '.'
)
$path_fonts = "$env:LOCALAPPDATA\Microsoft\Windows\Fonts"
$Registry = "HKCU:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"
function WriteLog {
Param ([string]$LogString)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
$LogMessage = "$Stamp $LogString"
Add-content $LogFile -value $LogMessage
}
$Logfile = "$env:LOCALAPPDATA\Temp\fonts_install.log"
WriteLog "Silent $Silent"
WriteLog "location $location"
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName PresentationCore
$SourceFolder = "$location\Fonts_Install"
$WindowsFonts = [System.Drawing.Text.PrivateFontCollection]::new()
$Fonts = Get-ChildItem -Path $SourceFolder -Include *.ttf, *.otf -Recurse -File
ForEach ($Font in $Fonts) {
$Font_Name = $Font.Name
$font_fullname = $Font.fullname
if (Test-Path -PathType Leaf -Path "$path_fonts\$Font_Name") {
WriteLog "Previously installed $Font_Name"
}
else {
Copy-Item $Font -Destination "$path_fonts" -Force -Confirm:$false -PassThru
$WindowsFonts.AddFontFile("$font_fullname")
$ValueFont = "$path_fonts" + "\" + "$Font_Name"
$Typeface = New-Object -TypeName Windows.Media.GlyphTypeface -ArgumentList "$font_fullname"
[string]$FamilyFaceNames = $Typeface.FamilyNames.Values + $Typeface.FaceNames.Values
$RegistryValue = #{
Path = $Registry
Name = $FamilyFaceNames
Value = $ValueFont
}
if (Test-Path $Registry\$FamilyFaceNames) {
Remove-ItemProperty -name $FamilyFaceNames -path $Registry
}
New-ItemProperty #RegistryValue
WriteLog "New fonts installed $Font_Name"
}
}
switch ($Silent) {
$false {
if ($Error.Count -gt 0) {
for ($i = 0; $i -le ($Error.Items.Count + 1); $i++) {
$errMSG = "$Error"
}
[System.Windows.Forms.MessageBox]::Show("$errMSG", "Error", "OK", "Error")
}
else {
[System.Windows.Forms.MessageBox]::Show("ок", "Fonts", "OK", "Asterisk") | out-null
}
}
}
Unfortunately, specifying pass-through arguments via Start-Job's -ArgumentList (-Args) is limited to positional arguments, which prevents binding [switch] parameters, whose arguments must by definition be named.
As a workaround, instead of using -FilePath, invoke your script via the -ScriptBlock parameter. Inside of a script block ({ ... }, named arguments may be used in script calls, as usual:
Start-Job -ScriptBlock {
# Set the current location to the same location as the caller.
# Note: Only needed in *Windows PowerShell*.
Set-Location -LiteralPath ($using:PWD).ProviderPath
.\Fonts_Install.ps1 -Silent:$using:Silent $using:Location
} | Receive-Job -Wait -AutoRemoveJob
Note the use of the $using: scope in order to embed variable values from the caller's scope in the script block that will execute in the background.
You still need to refer to the -Silent parameter by name, and the whether the switch is on or off can be communicated by appending :$true or :$false to it, which is what :$using:Silent does.
In Windows PowerShell, background jobs execute in a fixed location (working directory), namely the user's Documents folder, hence the Set-Location call to explicitly use the same location as the caller, so that the script file can be referenced by a relative path (.\). This is no longer necessary in PowerShell (Core) 7+, which now thankfully uses the same location as the calller.
Here is a different alternative to mklement0's helpful answer, this answer does not use Start-Job and uses a PowerShell instance instead, using this method we can leverage the automatic variable $PSBoundParameters.
Do note, that for this to work properly, both .ps1 scripts must share the same parameter names or Alias Attribute Declarations that matches the same parameter from the caller. See this answer for more details.
You can use these snippets below as a example for you to test how it works.
caller.ps1
param(
[string] $Path = 'C:\Users',
[switch] $Silent
)
try {
if(-not $PSBoundParameters.ContainsKey('Path')) {
$PSBoundParameters['Path'] = $Path
}
$ps = [powershell]::Create().
AddCommand('path\to\myScript.ps1').
AddParameters($PSBoundParameters)
$iasync = $ps.BeginInvoke()
# Do something else here while the .ps1 runs
# ...
# Get async result from the PS Instance
$ps.EndInvoke($iasync)
}
finally {
if($ps -is [IDisposable]) {
$ps.Dispose()
}
}
myScript.ps1
# Note, since we're bounding this parameters from the caller.ps1,
# We don't want to assign Default Values here!
param(
[string] $Path,
[switch] $Silent
)
foreach($param in $MyInvocation.MyCommand.Parameters.Keys) {
[pscustomobject]#{
Parameter = $param
Value = Get-Variable $param -ValueOnly
}
}
A few examples:
PS /> .\caller.ps1
Parameter Value
--------- -----
Path C:\Users
Silent False
PS /> .\caller.ps1 -Path hello
Parameter Value
--------- -----
Path hello
Silent False
PS /> .\caller.ps1 -Path world -Silent
Parameter Value
--------- -----
Path world
Silent True
I guess this is a newbie question. Yet I did not find anything online...
I'm creating a small powershell script with a very simple gui.
This is the relevant part of the script:
foreach ($script in $scripts){
$btn = New-Object System.Windows.Forms.Button
#Text, Location, Size omitted
#Add clickhandler
$btn.Add_Click(
{
Write-Host "Clicked on Btn $script"
Start-Process -FilePath "powershell" -ArgumentList "-command", "`"$script`""
Write-Host "finished"
$Form.Close();
}
)
$Form.Controls.Add($btn)
}
Obviously $scripts contains paths pointing towards other powershell scripts.
Being a Java developer I was naiv enough to suspect every click handler to be created with its own reference to a script location ($script).
But of course powershell does not evaluate $script until the handler is invoked. Thus, every button will call the last element in my array $scripts since $script will reference the last element in $scripts after the loop completes.
How can I create a click handler inside a foreach-loop based on the loop-variable itself?
Solution
Mathias R. Jessen pointed me to a working solution. .GetNewClosure() called on the handler worked.
Working Code:
foreach ($script in $scripts){
$btn = New-Object System.Windows.Forms.Button
#Text, Location, Size omitted
#Add clickhandler
$btn.Add_Click(
{
Write-Host "Clicked on Btn $script"
Start-Process -FilePath "powershell" -ArgumentList "-command", "`"$script`""
Write-Host "finished"
$Form.Close();
}.GetNewClosure()
)
$Form.Controls.Add($btn)
}
Solution
Mathias R. Jessen pointed me to a working solution. .GetNewClosure() called on the handler worked.
Working Code:
foreach ($script in $scripts){
$btn = New-Object System.Windows.Forms.Button
#Text, Location, Size omitted
#Add clickhandler
$btn.Add_Click(
{
Write-Host "Clicked on Btn $script"
Start-Process -FilePath "powershell" -ArgumentList "-command", "`"$script`""
Write-Host "finished"
$Form.Close();
}.GetNewClosure()
)
$Form.Controls.Add($btn)
}
I am trying to figure out how to write a powershell script that will set all .swf extensions to open up on Internet Explorer. I was trying to do this with a command prompt similar to the example below. Unfornately my boss is requiring this to be done through powershell. Any help with this would be greatly appreciated since I have a txt file that will loop through about 400 computers and need to make these changes on.
CMD Way
C:\>ASSOC .swf
.swf=ShockwaveFlash.ShockwaveFlash
C:\>FTYPE ShockwaveFlash.ShockwaveFlash
ShockwaveFlash.ShockwaveFlash="C:\bin\FlashPlayer.exe" %1
What I am Trying:
Function Get-FileName{
[CmdletBinding()]
Param(
[String]$Filter = "|*.*",
[String]$InitialDirectory = "C:\")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $InitialDirectory
$OpenFileDialog.filter = $Filter
[void]$OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
$file = Get-FileName -InitialDirectory $env:USERPROFILE\Desktop -Filter "Text files (*.txt)|*.txt|All files (*.*)|*.*"
ForEach ($item in (Get-Content $file)) {
$sitem = $item.Split("|")
$computer = $sitem[0].Trim()
$user = $sitem[1].Trim()
cmd /c assoc .swf=InternetExplorer.Application
### Will the above line automatically install on every pc? ###
}
Any help with trying to insert how to change the FTYPE in powershell so that $computer can cycle through would be greatly appreciated!
ASSOC and FTYPE are CMD.exe built-in commands, not executables, which means they can only be run in the context of CMD. The easiest way to run them is to invoke CMD from PowerShell.
cmd /c assoc .swf
cmd /c ftype ShockwaveFlash.ShockwaveFlash
If you need a "pure" PowerShell implementation, then you need to go to the registry. ASSOC and FTYPE merely write to the registry under theHKEY_CLASSES_ROOT hive. PowerShell does not have a default PSDrive for HKCR:, but that hive is also accessible under HKLM:\Software\Classes.
$ext = '.swf'
$HKCR = 'HKLM:\Software\Classes'
$ftype = Get-ItemProperty -Path "$HKCR\$ext" | select -expand '(default)'
$commandLine = Get-ItemProperty -Path "$HKCR\$ftype\shell\open" | select -expand '(default)'
$commandLine
To update these values, you simply use Set-ItemProperty on the same path.
Set-ItemProperty -Path "$HKCR\$ext" -Name '(default)' -Value 'ShockwaveFlash.ShockwaveFlash'
This requires you to run with Admin privileges. This also assumes that the key already exists. If not, you will have to create it with New-Item
if (-not (Test-Path "$HKCR\$ext")) {
New-Item -Path "$HKCR\$ext"
}
However, if all you want to do is set .swf files to open in iexplore.exe, then retrieving the values is unnecessary, as is modifying the FTYPE key. You need only change the extension association to InternetExplorer.Application instead of ShockwaveFlash.ShockwaveFlash. The following full scripts will do this:
In Batch file:
assoc .swf=InternetExplorer.Application
In PowerShell:
cmd /c assoc .swf=InternetExplorer.Application
In "pure" PowerShell, by modifying the registry:
$key = "HKLM:\Software\Classes\.swf"
$defaultName = '(default)'
$newValue = 'InternetExplorer.Application'
if (-not (Test-Path $key)) {
New-Item -Path $key
}
Set-Itemproperty -Path $key -Name $defaultName -Value $newValue
Note that modifying the registry doesn't take effect immediately. You need to also send a WM_SETTINGCHANGE event, or simply restart explorer.exe (eg: by logging off). You can find code to send the event here, but usually this isn't a problem for automated scripts because they force the user to re-login anyway.
I have seen the post "How to properly use the FolderBrowserDialog in Powershell"
I am having an issue getting just the path selected to return from the function.
At the end of the script I "write-host $a" but instead of getting just the directory I selected (C:\Temp) I get
System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 C:\Temp
Function Get-Folder($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
$foldername = New-Object System.Windows.Forms.FolderBrowserDialog
$foldername.rootfolder = "MyComputer"
if($foldername.ShowDialog() -eq "OK")
{
$folder += $foldername.SelectedPath
}
return $folder
}
$a = Get-Folder
Write-Host $a
I was told this is obsolete and to use Add-Type. Not getting just the path with the following script.
Add-Type -AssemblyName System.Windows.Forms
$FolderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog
[void]$FolderBrowser.ShowDialog()
$FolderBrowser.SelectedPath
Write-Host "FolderBrowser= "$FolderBrowser
You are getting that result because this line also produces output:
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
PowerShell returns all output from a function so your results are actually an array containing the output from the loading of the assembly and the folder name.
Adding [void] in front of the assembly loading operation like this will omit that extra output and give you the results you are expecting:
[void][System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms")
Or as Anthony Stringer mentions in the comments, you can use Add-Type instead which does not produce any output and would probably be the preferred method:
Add-Type -AssemblyName System.Windows.Forms
Also, TheMadTechnician is correct that you don't need the +=, just the = for the $folder variable.
This answer explains the behavior of returning output from a PowerShell function in more detail.
Back in cmd.exe, I used
set /P file=Enter path to the file:
to enter file path from console (if it wasn't supplied as a parameter), and I could press Tab to get path auto-completion. However, when I execute in Powershell
$file = Read-Host -Prompt "Enter path to the file"
then I cannot use Tab to get auto-completion, it just inserts a tabulation in the input. IS there a way to simulate the former behaviour?
I know, I know... not really an answer to your question directly, but still totally worth mentioning IMHO. Why ask the user to type out a path (and chance typos) when you can just pop up a Open File dialog box? Drop this function at the beginning of the script:
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
}
Then when you need to get a file name and path you can just do $file = get-filename and be done with it. If you only want certain file types you can change the filter line to only allow the user to see certain kinds of files, or even a specific file name (i.e. you need them to locate 'computerlist.csv' on the hard drive or something, you can change the . in the filter to computerlist.csv).
Based on the idea of JG in SD, the version of the selection folder is given here.
function Get-FolderPath($initialDirectory)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") > $null
$FolderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog
$FolderBrowserDialog.SelectedPath = $initialDirectory
$FolderBrowserDialog.ShowDialog() > $null
$FolderBrowserDialog.SelectedPath
}
Here is an updated (PSVersion 5.1 and newer) version of JG in SD's post:
function Get-FileName {
param
(
$initialDirectory
)
$null = Add-Type -AssemblyName System.windows.forms
$OpenFileDialog = New-Object -TypeName System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = 'All files (*.*)| *.*'
$null = $OpenFileDialog.ShowDialog()
$OpenFileDialog.filename
}
I resolved this by using cmd.exe. I could not find a way to capture the output directly without powershell somehow disabling command completion, so I had to use Invoke-Expression and a temp file to pass back the result.
Invoke-Expression 'cmd /v:on /c set /P file=Enter target path: `& if defined file echo !file! `> %TEMP%\temp.tmp'
$TargetPath = $null
If ( Test-Path -PathType Leaf "$ENV:TEMP\temp.tmp" ) { $TargetPath = (Get-Content "$ENV:TEMP\temp.tmp").Trim() 2>$null }
Remove-Item "$ENV:TEMP\temp.tmp" 2>$null