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.
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
Write-Output "$Server"
write-output "$username"
Copy-Item $Server -Destination "$env:USERPROFILE\AppData\Roaming\Microsoft\Signatures\" -Force
Test-path -path "$env:USERPROFILE\AppData\Roaming\Microsoft\Signatures\$username.htm" -PathType Leaf
Try
{
$MSWord = New-Object -com word.application
$EmailOptions = $MSWord.EmailOptions
$EmailSignature = $EmailOptions.EmailSignature
$EmailSignature.NewMessageSignature='$Username'
$EmailSignature.ReplyMessageSignature='$Username'
$MSWord.Quit()
}
Catch {
Add-Type -AssemblyName PresentationCore,PresentationFramework
[System.Windows.MessageBox]::Show("A mistake has appears")
Exit
}
Finally {
Clear-variable -Name "Name"
Clear-variable -Name "Username"
}
Hi ! That code was working just fine last week. But now, can't change the Outlook's setting with it. I can confirm the outputs give the right variables, and the test-path is true. The "Server" variable is were the file .htm is, with the variable "Username" to find the good .htm, and then copy it in the profile. Then, test if the file is in the Outlook profile of the user. Like I say, everything here give True for the test. But the script can't then install the .htm as a default signature. I change nothing, everything was working fine until now.
Any idea what could have happen. The file copy in the good folder, but the script always got stuck in the word.application part.
Thanks for the help.
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
})
I am trying to get a full list of senders to an specific shared mailbox i have access to (vía Outlook).
So far I am using this small but handy script to choose the selected folder as I have no idea on how to call as a parameter (Param) the folder I want to check:
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$Inbox = $namespace.pickfolder()
$Inbox.items | Select-Object -Property SenderName
After that I filter the output depending on the info I need (in this case, senders).
This forces me to choose the folder MANUALLy, I just want to set it as a parameter to the script.
How should I pass the specific folder as a parameter?.
Thanks in advance and best regards.
If I understood that correctly, you yould create a function where you pass the Inbox Name
function GetSenders
{
[CmdletBinding()]
Param
(
[Parameter(Mandatory=$True)]
[string]$Inbox
)
Begin
{
Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$olFolders = "Microsoft.Office.Interop.Outlook.olDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
}
Process
{
"Your Script here - What should happen with the Inbox?"
}
End
{
Write-Host "Script has Finished"
}
}
When you load this Function, you can just write GetSenders -Inbox "InboxName"
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