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

21,324

Solution 1

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

Solution 2

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.

Solution 3

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)

Solution 4

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.

Solution 5

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: [email protected]

    MODIFIED:06/11/2015
    Author: [email protected]   -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"
Share:
21,324
Anthony Mastrean
Author by

Anthony Mastrean

Updated on December 06, 2020

Comments

  • Anthony Mastrean
    Anthony Mastrean over 3 years

    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.

    Installing Fonts The font is already installed

    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)
    
  • Anthony Mastrean
    Anthony Mastrean over 11 years
    I tried $options = "0x{0:X}" -f (4 -bor 16) and all other combinations of single and multiple flags. No luck, same dialogs.
  • Keith Hill
    Keith Hill over 11 years
    Perhaps you skip the Options and check to see if the font exists and only invoke CopyHere if the font doesn't exist. Look at the reply Jason Ladwig makes June 8, 2012 wrote: social.technet.microsoft.com/Forums/en-US/winserverpowershel‌​l/…
  • Keith Hill
    Keith Hill over 11 years
    FYI, there are some folks that claim the Options parameter is worthless. :-) mattclingan.wordpress.com/2007/08/08/…
  • Yoshi
    Yoshi about 11 years
    Did you try Remove-Item with -Force to see if the "font in use" prompt goes away? I found a reliable script here that will remove existing fonts first, and it handles multiple font style names: social.technet.microsoft.com/Forums/fr-FR/winserverpowershel‌​l/…
  • Otryks
    Otryks about 10 years
    The disclaimer is clear on MSDN page: Note In some cases, such as compressed (.zip) files, some option flags may be ignored by design. msdn.microsoft.com/en-us/library/ms630707.aspx
  • Northstrider
    Northstrider about 7 years
    Very interesting approach but it kinda unfortunate that you cant use PC while it's running and it doesnt auto cancel the job at the end
  • Anthony Mastrean
    Anthony Mastrean over 5 years
    There is a custom script posted in 2010 by a senior Microsoft consultant that seems to work by using some custom .NET code?! blogs.technet.microsoft.com/deploymentguys/2010/12/04/…
  • cklm
    cklm almost 4 years
    the two options do not work when copying to windows-fonts-directory...