Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

powershell

com

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 FontsThe 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)
like image 497
Anthony Mastrean Avatar asked Aug 28 '12 19:08

Anthony Mastrean


People also ask

How do I copy a folder in PowerShell?

You can run the PowerShell scripts or commands to automate folder copying tasks. You might also have to copy a folder’s subfolders and contents when copying it. The Copy-Item cmdlet copies an item from one location to another. You can use this cmdlet to copy the folder and its contents to the specified location.

How do I copy a file from c:\FSO to C:\fsox using PowerShell?

“Open your Windows PowerShell console, and use Copy-Item to copy any file from your C:\fso folder to your C:\fsox folder,” I said. She thought for about a second and typed the following until she had a file selected: Next she typed the following: -d<tab><space>c:\fsox\mylog.log<enter> The completed command is shown here:

How to copy folder content from one location to another?

Mostly, system admins have to carry out various files and folders operations. Copying folder content from one location to another is one of them. PowerShell supports the handling of different folder operations in the system. Using the PowerShell console, you can create, copy, move, rename, and delete folders.

How to copy and move folders in Windows 10?

Using the PowerShell console, you can create, copy, move, rename, and delete folders. Copying folders is simple using the copy and paste option or drag and drop feature in a few clicks. But, it will take a long time when you have a lot of folders to copy to the various locations.


6 Answers

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.

like image 116
ubie Avatar answered Oct 05 '22 14:10

ubie


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.

like image 44
Keith Hill Avatar answered Oct 05 '22 14:10

Keith Hill


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)
like image 23
user714429 Avatar answered Oct 05 '22 14:10

user714429


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

like image 43
Anthony Mastrean Avatar answered Oct 05 '22 13:10

Anthony Mastrean


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"
like image 30
FoxDeploy Avatar answered Oct 05 '22 12:10

FoxDeploy


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"
}
like image 45
Shital Shah Avatar answered Oct 05 '22 14:10

Shital Shah