How do I pass option flags to Folder.CopyHere in PowerShell? - powershell

I am trying to write a script that automatically and silently moves a bunch of fonts into the Fonts special folder so they are available as if you had "installed" them from Explorer (by dragging and dropping, copying, or right-click and choosing Install). I have the Shell.Application part down all the way to the copy.
$FONTS = 0x14
$shell = New-Object -ComObject Shell.Application
$source = $shell.Namespace($downloaded_path)
$target = $shell.Namespace($FONTS)
$target.CopyHere($source.Items())
However, some systems may already have the fonts installed and I want the progress dialog to be hidden and any prompts to be silently accepted.
So, I'm investigating the Folder.CopyHere option flags.
4 Do not display a progress dialog box
16 Respond with "Yes to All" for any dialog box that is displayed.
I hope they are supported in this folder (some options are ignored by design). And I think these are in decimal, right? Do they need to be converted? However I pass them in, I still see both dialogs. I have tried
$options = 4 <-- don't expect int to work
$options = 0x4 <-- thought hexidecimal would be ok, the VB documentation shows &H4&
$options = "4" <-- string's the thing?
$options = [byte]4 <-- no luck with bytes
$options = [variant]4 <-- this isn't even a type accelerator!
And, if I can get one option working, how do I get both working? Do I bor them together? What about the formatting?
$options = 4 -bor 16
Or do I add them or convert them to hex?
$options = "{0:X}" -f (4 + 16)

You can use 4 -bor 16. It is hard to tell what this method expects since the type is VARIANT. I would have thought that it would take an integer value. If that doesn't work, this comment from the MSDN topic on Folder.CopyHere implies that a string should work:
function CopyFileProgress
{
param( $Source, $DstFolder, $CopyType = 0 )
# Convert the decimal to hex
$copyFlag = [String]::Format("{0:x}", $CopyType)
$objShell = New-Object -ComObject "Shell.Application"
$objFolder = $objShell.NameSpace($DestLocation)
$objFolder.CopyHere($Source, $copyFlag)
}
Although I wonder if the format string should be "0x{0:x}"?
Just be aware that for normal .NET flags style enums, you can pass multiple flags to a .NET (or command parameter) that is strongly typed to the enum like so:
$srv.ReplicationServer.Script('Creation,SomeOtherValue')
Oisin has written up some info on this subject in this blog post.

I had the same problem and found this in another thread, Worked perfectly for me.
If you want it to overwrite AND be silent change 0x10 to 0x14 (docs).
$destinationFolder.CopyHere($zipPackage.Items(), 0x14)

The Folder.CopyHere option flags may simply not work. This makes me sad. I'll have to investigate one of these other methods, all of which leave me in a bit of a bind.
Separate Process
Invoke the copy in a new process and hide the window using the ProcessStartInfo properties. I haven't implemented this yet, but I wonder if it will address the user-prompting for overwriting existing files?
Dim iProcess As New System.Diagnostics.ProcessStartInfo(AppDomain.CurrentDomain.BaseDirectory + “unzip.exe”)
iProcess.CreateNoWindow = True
Dim sArgs As String = ZippedFile
iProcess.Arguments = sArgs
iProcess.WindowStyle = ProcessWindowStyle.Hidden
Dim p As New System.Diagnostics.Process
iProcess.UseShellExecute = False
p = System.Diagnostics.Process.Start(iProcess)
p.WaitForExit(30000)
Dim s As Integer = p.ExitCode
iProcess.UseShellExecute = True
p.Dispose()
iProcess = Nothing
For Loop
Only copy non-existing items. This seems to fall down when I actually want to update an existing font with a new font file of the same name.
foreach($File in $Fontdir) {
$fontName = $File.Name.Replace(".ttf", " Regular")
$objFolderItem = $objFolder.ParseName($fontName);
if (!$objFolderItem) {
$objFolder.CopyHere($File.fullname,0x14)
}
}
Remove Existing
I'm thinking of removing all fonts of the same name as the ones I'm copying, then copying the set. Although that's kind of brutal. And I believe that there's another prompt if that font cannot be deleted because it's in use. sigh

The copy flags don't work for me. I setup a job in the install fonts script that detects the "Installing Fonts" window and send {Enter} to it so I am not overwriting existing fonts.
Start-Job –Name DetectAndClosePrompt –Scriptblock {
$i=1
[void] [System.Reflection.Assembly]::LoadWithPartialName("'System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("'Microsoft.VisualBasic")
while ($i -eq 1) {
$windowPrompt = Get-Process -ErrorAction SilentlyContinue |? {$_.MainWindowTitle -like "*Installing Fonts*"}
[Microsoft.VisualBasic.Interaction]::AppActivate($windowPrompt.ID)
[System.Windows.Forms.SendKeys]::SendWait("{Enter}")
sleep 2
}
}
After all fonts are copied/installed... I remove the job, by name.
Get-Job DetectAndClosePrompt | Remove-Job -Force
That works for me on Windows 7, 8.x, & 10.

I'm seeing a number of Unzip folder operations, but really no one writing a solution to fit the Fonts folder situation. So I wrote my own! As it turns out, the Fonts folder does implement the Shell.Folder.CopyHere method, but does not honor any overloads passed for the second argument of the method. Why? Who knows! I suspect Raymond Chen of 'The Old new Thing' Windows Developer blog could explain it, but I don't know the answer. So we need instead to intelligently look for our fonts before trying to copy them, or we'll get a nasty message.
In my code, we check to see a font exists or not by checking for a match on the first four characters of the font name with a wildcard search. If the font doesn't exist, we assume this is the first time we're installing fonts on this system and set a special flag called $FirstInstall.
From then on in the script, if $FirstInstall is true, we install every font in the source font directory. On subsequent executions, we check to see if each font is a match, and if so, we abort that copy. If not, we go ahead and copy. This seems to work for most of my clients, thus far.
Here you go!
<#
.SYNOPSIS
Script to quietly handle the installation of fonts from a network source to a system
.DESCRIPTION
We Can't just move files into the %windir%\Fonts directory with a script, as a simple copy paste from command line doesn't trigger windows to note the new font
If we used that approach, the files would exist within the directory, but the font files woudln't be registered in windows, nor would applications
display the new font for use. Instead, we can make a new object of the Shell.Application type (effectively an invisible Windows Explorer Windows) and use its Copy method
Which is the functional equivalent of dragging an dropping font files into the Font folder, which does trigger the font to be installed the same as if you right clicked the font
and choose install.
.PARAMETER FontPath
The path of a folder where fonts reside on the network
.EXAMPLE
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"
Installing font...C:\temp\Noto\NotoSans-Bold.ttf
Installing font...C:\temp\Noto\NotoSans-BoldItalic.ttf
Installing font...C:\temp\Noto\NotoSans-Italic.ttf
Installing font...C:\temp\Noto\NotoSans-Regular.ttf
In this case, the fonts are copied from the network down to the system and installed silently, minus the logging seen here
import files needed for step 1, step 2, and step 5 of the migration process.
.EXAMPLE
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"
Font already exists, skipping
Font already exists, skipping
Font already exists, skipping
Font already exists, skipping
In this case, the fonts already existed on the system. Rather than display an annoying 'Overwrite font' dialog, we simply abort the copy and try the next file
.INPUTS
String.
.OUTPUTS
Console output
.NOTES
CREATED: 06/11/2015
Author: sowen#ivision.com
MODIFIED:06/11/2015
Author: sowen#ivision.com -Reserved...
#>
param
(
[Parameter(Mandatory)][string]$FontPath="C:\temp\Noto"
)
#0x14 is a special system folder pointer to the path where fonts live, and is needed below.
$FONTS = 0x14
#Make a refrence to Shell.Application
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
ForEach ($font in (dir $fontsPath -Recurse -Include *.ttf,*.otf)){
#check for existing font (to suppress annoying 'do you want to overwrite' dialog box
if ((($objShell.NameSpace($FONTS).Items() | where Name -like "$($font.BaseName.Split('-')[0].substring(0,4))*") | measure).Count -eq 0){
$firstInstall = $true}
if ($firstInstall -ne $true) {Write-Output "Font already exists, skipping"}
else{
$objFolder.CopyHere($font.FullName)
Write-Output "Installing font...$($font.FullName)"
$firstInstall = $true
}
}
.\Install-Fonts.ps1 -FontPath "\\corp\fileshare\Scripts\Fonts"

There are several issues with #FoxDeploy's answer which is why it is not working. First issue is that you also want to check Fonts folder in %USERPROFILE% or you would get confirmation dialog. Second issue is that you want to avoid assuming '-' in font name.
Below is the fixed version that installs fonts from CodeFonts repo as an example:
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.Drawing
# Clone chrissimpkins/codeface from which we will install fonts
if (!(Test-Path /GitHubSrc/codeface)){
git clone git://github.com/chrissimpkins/codeface.git /GitHubSrc/codeface
}
#0x14 is a special system folder pointer to the path where fonts live, and is needed below.
$FONTS = 0x14
$fontCollection = new-object System.Drawing.Text.PrivateFontCollection
#Make a refrence to Shell.Application
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
# local path
$localSysPath = "$Env:USERPROFILE\AppData\Local\Microsoft\Windows\Fonts"
$localSysFonts = Get-ChildItem -Path $localSysPath -Recurse -File -Name | ForEach-Object -Process {[System.IO.Path]::GetFileNameWithoutExtension($_)}
$fontsPath="\GitHubSrc\codeface\fonts"
ForEach ($font in (dir $fontsPath -Recurse -Include *.ttf,*.otf)){
if ($localSysFonts -like $font.BaseName) {
Write-Output "SKIP: Font ${font} already exists in ${localSysPath}"
}
else {
$fontCollection.AddFontFile($font.FullName)
$fontName = $fontCollection.Families[-1].Name
#check for existing font (to suppress annoying 'do you want to overwrite' dialog box
if ((($objShell.NameSpace($FONTS).Items() | where Name -ieq $fontName) | measure).Count -eq 0){
Write-Output "INST: Font ${font}"
$objFolder.CopyHere($font.FullName)
$firstInstall = $true
}
else {
Write-Output "SKIP: Font ${font} already exists in SYSTEM FONTS"
}
}
# Read-Host -Prompt "Press Enter to continue"
}

You can just take a sum of your options. I was need to run CopyHere with two options - SILENT and NOCONFIRMATION. Look at the sample below:
function Unzip-Archive($targetpath, $destination)
{
$shell_app=new-object -com shell.application
$FOF_SILENT_FLAG = 4
$FOF_NOCONFIRMATION_FLAG = 16
$zip_file = $shell_app.namespace("$targetpath")
#Set the destination directory for the extracts
$destination = $shell_app.namespace("$destination")
#unzip the files
$destination.Copyhere($zip_file.items(), $FOF_SILENT_FLAG + $FOF_NOCONFIRMATION_FLAG)
}

I just got this to work by simply using + i.e.
function Expand-ZIPFile($file, $destination)
{
$shell = new-object -com shell.application
$zip = $shell.NameSpace($file)
foreach($item in $zip.items())
{
$shell.Namespace($destination).copyhere($item, 16+1024)
}
}

Related

Get localized file (resource) names, as shown in Windows Explorer

I am looking for a PowerShell function like Get-LocalizedName($FilePath), returning the localized name of a file or its filename if it is not localized. I know that the localized names are stored in the LocalizedFileNames section of the respective desktop.ini files, but usually as resource file pointers rather than clear names.
Example: For the Administrative Tools folder and the locale de-DE, I want the clear name Windows-Verwaltungsprogramme instead of #%SystemRoot%\system32\shell32.dll,-21762.
I was not able to find such a function, and also was not successful in analyzing the attributes of Get-ChildItem or google a regarding solution.
Is there any such function that I could use from PowerShell (v7)?
The following solution works for folders only. See this answer for a solution that works for files, localized via LocalizedFileNames section of Desktop.ini.
This can be done using the Shell.Application COM object:
$shell = New-Object -ComObject Shell.Application
# Get full path to the admin tools folder
$adminToolsPath = [Environment]::GetFolderPath('AdminTools')
# Get the shell folder corresponding to this path
if( $folder = $shell.NameSpace( $adminToolsPath ) ) {
$folder.Title # Output localized title
}
# Alternative:
if( $folder = $shell.NameSpace( [Environment+SpecialFolder]::AdminTools ) ) {
$folder.Title # Output localized title
}
[Environment]::GetFolderPath() gives us the filesystem path of a system folder.
The Shell.Namespace() function returns a Folder object corresponding to this path which can be queried for its localized name.
The alternative shows how you can get the localized name more directly, by passing an enumeration value of [Environment+SpecialFolder] to the Shell.Namespace() function.
When passing a path to the Shell.Namespace() method, it works for any folder customized via "desktop.ini", even if it's not a system folder.
I finally found a solution for Get-LocalizedName, digging into 20 years old VBS code using GetDetailsOf:
function Get-LocalizedName {
Param([Parameter(Mandatory=$True)][string]$FilePath)
$ChildObj = Get-ChildItem $FilePath
$FldrName = $ChildObj.DirectoryName
$FileName = $ChildObj.Name
$Shell = New-Object -ComObject Shell.Application
$Folder = $Shell.Namespace($FldrName)
$File = $Folder.ParseName($FileName)
return $($Folder.GetDetailsOf($File,0))
}

See metadata variable names on files (Windows 10)

I am having a hard time defining this issue, but basically what I would like to know is "what are the symbolic variable names connected to files' metadata (preferably on a Windows installation)".
For example, taking a .mp3 file, checking its properties yields a Title, Bit Rate, Folder Path etc. descriptions. What I want to know is the name of the fields seen by programs (i.e. Title->title, Bit Rate->bit_rate etc.) if it makes any sense, as I've been trying to index some files and I'd like to gather as much info on them as possible.
I'm not convinced that there is such a thing as "symbolic names" for the metadata, especially not in relation to PowerShell. I suspect that Windows maintains support for a certain number of popular formats, and offers functionality through Explorer to view and sometimes edit them. I haven't found a source to prove this theory, but research implicitly supports it: there's several dozen search results about how to retrieve a file's metadata in PowerShell, and they all seem to suggest roughly the same approach (for example this blog post): using a Shell object to gather the information.
Since you tagged this PowerShell, here's my take on boiling it down to the essentials:
$path = 'C:\temp\file.txt' # pick a path
$parent = Split-Path -Parent $path # get the directory
$shell = New-Object -ComObject Shell.Application # get ourselves a shell
$folder = $shell.NameSpace($parent) # get a "folder namespace"
$file = $Folder.Items() | where { $_.Path -eq $path } # get the file itself from the folder
$count = 0 # zero our iterator
$object = New-Object PSObject # make a fresh object to hold our output
While ($folder.GetDetailsOf($folder.Items, $count) -ne "") { # iterate over the available metadata tags for the folder, and for each one get the value from the file
$object | Add-Member -Force NoteProperty ($folder.GetDetailsOf($folder.Items, $count)) ($folder.GetDetailsOf($file, $count))
$count += 1
}
Write-Output $object
Note that the attributes available for a given file are obviously not all of the attributes that could possibly be supported for any file, and additionally are not necessarily "symbolic names". I suspect that the process of querying the shell object causes it to examine the files in a folder and extract metadata that Windows recognizes--it might even do this based on the view type selected for the folder (Photos, Music, Documents, etc.).
As for writing the information, this might be possible through the same shell object, but I haven't explored that option. It's likely dependent on the specific format: for mp3 you probably want a library for viewing/editing mp3-specific metadata.

How can I set the save location of a file using powershell?

I am trying to print a PDF as XPS, the script opens the PDF Print Output As screen and enters the correct name before sending the ENTER command to the window to save the file.
How can I select the address bar to enter the desired path? Or how can I change the default save path?
EDIT: Thank you for the feedback. Here is the script:
function print_files($secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names($xps_dir, $fullname){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys($xps_dir\$basename)
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files($secure_pdf_dir)
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
It seems that the $xps_dir variable is not being passed into the function properly.
Edit: Error I get when trying to add $xps_dir to the enter_my_names function:
Microsoft.PowerShell.Utility\Write-Error : Cannot validate argument on parameter
'FilePath'. The argument is null or empty. Provide an argument that is not null or
empty, and then try the command again.
At same_time:115 char:115
+
+ CategoryInfo : NotSpecified: (:) [Write-Error], ParameterBindingValidati
onException
+ FullyQualifiedErrorId : System.Management.Automation.ParameterBindingValidationEx
ception,Microsoft.PowerShell.Commands.WriteErrorCommand
+ PSComputerName : [localhost]
I'm not sure if you got this fully worked out or I misread your post, but I was able to implement some code from your initial post to finish my project [been beating my head against a wall as a non-traditional comp sci person]; so I wanted to share some things I did in hopes of helping you (in the event you're still at that crossroads).
Some points of interest:
-My overall process is a bulk report generator. A variable amount of .XML stylesheets are sent to IE because a javascript subroutine numbers the reports; to accomplish this I have visual basic doing some database parsing, passing values into custom scripts needed to form the .XML stylesheets, then VB creates PS .ps1 files with the various PS commands needed to create the IE com object. Finally, VB calls the .ps1 scripts, and the reports are generated by sending the stylesheet to IE, waiting for HTML render, send print command, and close the object.
-While your endgoal is to generate an XPS, mine was to fully render an HTML, then print it with a PDF printer; I assume we have a similar process in that we must engage a dialog box in the intermediate stage (in this case, using sendkeys to interact with it); this is specifically what I'm going to discuss below in hopes of helping out!
So, two points of discussion:
First, more of a general observation/query: I don't see where your $xps_dir variable is actually being defined; I mention this because there could be a string-related issue with the values being passed by the $xps_dir. One way to check your string to make sure its 'pretty' is to pass it to a .txt or something using the OUT-FILE command:
"$xps_dir" | Out-File -FilePath "C:\somefolder\sometext.txt" -Append
The text file itself doesn't need to exist, but the file does...the above command will create the text file.
Also, if you're ok with viewing the powershell prompt, a WRITE-HOST can be used.
Either way, I'm wondering if the final compiled string isn't compiled correctly, so its not recognizing it as a directory.
I think what more likely is happening is the SENDKEYS aren't being sent to the correct fields. For example, by using your code I was [FINALLY] able to set my 'Save As' dialog box as an object and interact with it. (When using my PDF printer, I get the 'Save As' dialog box).
At first, I tried to send the file name, then send enter. I assumed this would be fine, because during manual interaction the focus for the 'Save As' dialog box goes to the 'File Name' field; however, after trying:
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{ENTER}")
I realized the SendKeys command was sending the keys to the 'Save in' drop down (or probably the first element of the Save As dialog box). So, I manually click inside of the 'Save in' field, then tabbed around until I got to 1. 'File Name' (wrote the # of tabs down), and 2. the 'Save' button (wrote # of tabs down). By adding SendKeys("{TAB}") to correspond with the number of tabs I observed, I was able to successfully enter my name string and close out the print dialog box:
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
NOTE: the number of tabs might be different for your dialog box, so I strongly recommend doing a manual run through to count how many tabs get you to the field you want to interact with.
In conclusion, I have one reflection for consideration:
I've used (to much success) VB and PS to use html elements to dynamically enhance my routines. Since we're able to set the dialog box as a Com object, my next goal will be to try to tap into the respective 'elements', similar to how html elements can be accessed (or more generally, object-oriented language allows). This way, I might be able to better interact with the dialog box without suffering the drawbacks of SendKeys:
Has to run as active process; user cannot engage any other windows else keys might be sent to them;
Keys are based on a count; any variation to that count will require the subroutine to be updated/edited, making it not elastic;
Also, I have not done any work regarding error windows, but one I've already noticed is the "your document has the same name blah blah", so just a heads up.
Even though we're doing two different things, here's my final code for this part of my routine in case it helps provide context:
#send command to internet explorer application with execWB; 6 for print, 2 for no user prompt on window
while ( $ie.busy ) { Start-Sleep -second 3 }
$ie.execWB(6,2)
#create dialog box object
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save As') -ne $true){ Start-Sleep -second 3 }{
$wshell.AppActivate('Save As')
}
#create file string from 3 strings: directory,basename returned, file extension
#the return from my output log is: "c:\test_folder\mybasenamestring.PDF"
$mydirectory = "c:\test_folder\"
$mybasename = [io.path]::GetFileNameWithoutExtension($fullname)
$myextension = ".PDF"
$myfilename = "$mydirectory$mybasename$myextension"
#Using tabs to navigate around the dialog window; send my string to the 'File Name' field, then tab to 'Save' and send enter
#This is where the name is actually entered
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
#going to add a command to report on the file size of the pdf; the pdf will stay 0kb until its fully generated. Once fully generated will add the $ie.quit or the kill command here for the $ie object
#this sends a stop process command to kill powershell since I'm running it as a ps1 file and it will remain open otherwise
stop-process -Id $PID
Last thing: I observed some weirdness with the capitalization, so I updated the final SendKeys to capitalize the string since I want all caps anyhow:
$wshell.SendKeys($myfilename.toupper())
Hope this helps! Thanks for posting; I was finally able to complete my entire process. As I clean and improve, if I find improvements for this area I'll try to remember to share. Thanks for your help!
Pass the variable containing the desired directory ($xps_dir in this case) to each function in the process until it gets to the enter_my_names function where it can then be sent to the window with the $wshell.SendKeys("$xps_dir\$basename").
function print_files($xps_dir, $secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $xps_dir $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names{
param ($xps_dir, $fullname)
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys("$xps_dir\$basename")
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$xps_dir, $fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files $xps_dir $secure_pdf_dir
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
The file dialogue will accept a path in the file name field, so instead of sending the filename file.pdf send the full path to the file C:\folder\folder\file.pdf
EDIT:
You can send the folder path like so:
$wshell.SendKeys($xps_dir)
$wshell.SendKeys("{ENTER}")
$wshell.SendKeys($basename)
$wshell.SendKeys("{ENTER}")

Pass parameter from one Powershell function to another

I’m new to powershell and development in general. I’m trying to write a script that will email a contact once a file exceeds a certain size. I have two individual functions both working separately (one to check the file size and one to generate a file for sendmail to use) but I can’t get them to interact.
I want to execute the function CheckSize and if the variable $ExceedsSize gets set to 1 then call function SendMail otherwise the script should finish with no other action.
I’ve searched through the forums but couldn’t find anything to apply to what I’m doing.
##Check file to see if it is over a particular size and then send email once threshold is reached.
param(
[string]$SiteName = "TestSite", #Name of Site (No Spaces)
[string]$Path = "\\NetworkPath\Directory", #Path of directory to check
[int]$FileSizeThreshold = 10, #Size in MB that will trigger a notification email
[string]$Contacts = "MyEmail#email.com"
)
CLS
##Creates variable $ExceedsSize based on newest file in folder.
Function CheckSize {
IF ((GCI $Path -Filter *.txt | Sort LastWriteTime -Descending | Select-Object -first 1 | Measure-Object -property Length -sum).sum / 1000000 -gt $FileSizeThreshold) {$ExceedsSize = 1}
ELSE {$ExceedsSize = 0}
Write-Host $ExceedsSize
}
Function SendMail {
Param([string]$Template, [string]$Contacts, [string]$WarnTime)
$EmailLocation = "\\NetworkPath\Scripts\File_$SiteName.txt"
#Will Generate email from params
New-Item $EmailLocation -type file -force -value "From: JMSIssue#emails.com`r
To: $Contacts`r
Subject: $SiteName file has exceeded the maximum file size threshold of $FileSizeThreshold MB`r`n"
#Send Email
#CMD /C "$SendMail\sendmail.exe -t < $EmailLocation"
}
Add this before or after your Write-Host $ExceedsSize:
return $ExceedsSize
Add this to the bottom:
$var = CheckSize
if ($var -eq 1){
SendMail
}
Explanation
You have two functions, but don't actually run them. The part at the bottom does that.
Your CheckSize function does not return the $ExceedsSize for the rest of the function; by default it remains within the scope of the function. return x means the variable is passed back to the main script. $var = means it is assigned ot that variable.
Per the other answer, you need to return $ExceedsSize instead of using Write-Host (see here for why Write-Host is considered harmful: http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/).
You could alternatively call the SendMail function from within the CheckSize function, e.g:
if ($ExceedsSize -eq 1){SendMail}
You will still need to call the CheckSize function somewhere also:
CheckSize
You might also want to give consideration to naming your functions in the verb-noun style of the built in cmdlets. This really helps make their use more explicit to you and others. When choosing a verb, its best to stick to the approved list: https://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx
And also to use names that are fairly unique to avoid possible conflicts.
I'd suggest something along the lines of:
Get-NewestFileSize
(although that's what it should then return)
and
Send-CCSMail

Get file type description for file extensions

I make a report for data usage on my disk, I get info from all selected property like name, path, size... behalf one filedescription, for each scanned file this property is empty. For example when you select a file in windows Explorer and you select property in general tab you can see "Type of file", here for an Excel file the type of file is "Microsoft Excel Worksheet (.xlsx)".
gci c:\file | select *
How can i get this info?
I like to avoid external programs when I can, so I would suggestion using the registry.
$ext = ".xlsx"
$desc = (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$ext")."(default)")")."(default)"
$desc
Microsoft Excel-regneark #Norwegian description
To use it with Select-Object you can modify it like this:
#You could define this inside Select-Object too, but it's a bit long so I extracted it first to clean up the code.
$extensiondesc = #{n="ExtensionDescription";e={ (Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object Extension, $extensiondesc
Extension ExtensionDescription
--------- --------------------
.oxps XPS Document
.lnk Shortcut
.txt Text Document
Lets say $ext has the extension of the file.
For example -
$ext = ".bmp"
Following code will get you the description, if registered (you should add better error handling if appropriate for your scenario) -
$desc = (cmd /c assoc $ext).Split("=")[1]
$desc = (cmd /c assoc $desc).Split("=")[1]
Write-Host $desc
AFAIK, Powershell does not have any built in mechanism to get this information, and hence using cmd from powershell is the cheapest and easiest solution IMHO.
You could use the GetDetailsOf() method of the Shell.Application object:
$app = New-Object -COM 'Shell.Application'
$f = Get-Item 'C:\path\to\your\file'
$dir = $app.NameSpace($f.Directory.FullName)
$description = $dir.GetDetailsOf($dir.ParseName($f.Name), 2)
I have just improvised on Frode F's solution by adding a sorted list with unique entries to make it easier to read.
$extensiondesc = #{n="ExtensionDescription";
e={(Get-ItemProperty "Registry::HKEY_Classes_root\$((Get-ItemProperty "Registry::HKEY_Classes_root\$($_.Extension)")."(default)")")."(default)" }}
Get-ChildItem |
Select-Object -unique Extension, $extensiondesc |Sort-Object #{e="Extension";Ascending=$true},#{e="ExtensionDescription";Ascending=$false}