CopyHere method - powershell

I'm using:
$source = "C:\path1"
$destination = "C:\path2"
$FOF_CREATEPROGRESSDLG = "&H0&"
$objShell = New-Object -ComObject "Shell.Application"
$objFolder = $objShell.NameSpace($destination)
$objFolder.CopyHere($source, $FOF_CREATEPROGRESSDLG)
This is really nice since it copies files with the native Windows dialog for progress. However, if the files exist, it prompts for a confirmation.
How do I make it overwrite all files?
Reading this, it seems the flag is "16" for yes to all?
Not sure how to integrate it, I'm trying:
$objFolder.CopyHere($source, "16")
but it doesn't work.
https://learn.microsoft.com/en-us/windows/desktop/shell/folder-copyhere

Quotes cause PowerShell to automatically type their contents as a string. Simply use the numbers in that dialog and it should work properly.
This is because shell.application.CopyHere() method uses a bitmask to determine options. It's programmatically more efficient to compare binary registers against each other.
Try $objFolder.CopyHere( $source, 16 )

Ended up doing a re-write from some code I found, here is the final working version, progress bar shows but with no prompt stopying to copy if files exist already:
$source = "C:\path1"
$destination = "C:\path2"
$SourceNameSpace = (new-object -com shell.application).NameSpace($source)
$destinationFolder = (new-object -com shell.application).NameSpace($destination)
$destinationFolder.CopyHere($source,16)

you can add this at the end
set oWShell = createobject("wscript.shell")
do until oWShell.AppActivate("Confirm File Replace")
Wscript.Sleep 100
loop
oWShell.Sendkeys "{enter}"
it will automatically press enter to the prompts

Related

Embed a text file as an object in Ms Word 2010

I am building a script to write word documents using PowerShell (Windows 7, PS4, .Net 4, Office 2010.
The script works but it embeds the text at the top of the document. I need to be able to specify the exact location like on page 2.
Here's what I got up to date:
# Opens an MsWord Doc, does a Search-and-Replace and embeds a text file as an object
# To make this work you need in the same folder as this script:
# -A 2-page MsWord file called "Search_and_replace_Target_Template.doc" with:
# -the string <package-name> on page ONE
# -the string <TextFile-Placeholder> on page TWO
# -A textfile called "TextFile.txt"
#
#The script will:
# -replace <package-name> with something on page one
# -embed the text file <package-name> at the top of page one (but I want it to replace <TextFile-Placeholder> on page 2)
#
# CAVEAT: Using MsWord 2010
[String]$MsWordDocTemplateName = "Search_and_replace_Target_Template.doc"
[String]$TextFileName = "TextFile.txt"
[Int]$wdReplaceAll = 2
[Int]$wdFindContinue = 1 #The find operation continues when the beginning or end of the search range is reached.
[Bool]$MatchCase = $False
[Bool]$MatchWholeWord = $true
[Bool]$MatchWildcards = $False
[Bool]$MatchSoundsLike = $False
[Bool]$MatchAllWordForms = $False
[Bool]$Forward = $True
[Int]$Wrap = $wdFindContinue #The find operation continues when the beginning or end of the search range is reached.
[Bool]$Format = $False
[Int]$wdReplaceNone = 0
$objWord = New-Object -ComObject Word.Application
$objWord.Visible = $true #Makes the MsWord
[String]$ScriptDirectory = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.path)
[String]$WordDocTemplatePath = "$ScriptDirectory\$MsWordDocTemplateName"
[String]$TextFilePath = "$ScriptDirectory\$TextFileName"
[String]$SaveAsPathNew = Join-Path -Path $ScriptDirectory -ChildPath "${MsWordDocTemplateName}-NEW.doc"
#Open Template with MSWord
Try {
$objDoc = $objWord.Documents.Open($WordDocTemplatePath)
} Catch {
[string]$mainErrorMessage = "$($_.Exception.Message) $($_.ScriptStackTrace) $($_.Exception.InnerException)"
Write-Host $mainErrorMessage -ForegroundColor Red
Start-Sleep -Seconds 7
$objDoc.Close()
$objWord.Quit()
}
$objSelection = $objWord.Selection
$objSelection.Find.Forward = 'TRUE'
$objSelection.Find.MatchWholeWord = 'TRUE'
#Replace <package-name>
[String]$FindText = "<package-name>"
[String]$ReplaceWith = "PackageName_v1"
write-host "replacing [$FindText] :" -NoNewline
$objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Embed the text file as an object
[System.IO.FileSystemInfo]$TextFileObj = Get-item $TextFilePath
If ( $(Try {Test-Path $($TextFileObj.FullName).trim() } Catch { $false }) ) {
write-host "Embedding [$TextFileName] :" -NoNewline
[String]$FindText = "<TextFile-Placeholder>"
[String]$ReplaceWith = ""
# $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
#Need code to create a RANGE to the position of <TextFile-Placeholder>
#Embed file into word doc as an object
#$result = $objSelection.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
$result = $objSelection.Range[0].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true) #works too but does the same
Write-host "Success"
} Else {
Write-Host "[$TextFilePath] does not exist!" -ForegroundColor Red
Start-Sleep -Seconds 5
}
Write-Host "Saving updated word doc to [${MsWordDocTemplateName}-NEW.doc] ***"
Start-Sleep -Seconds 2
#List of formats
$AllSaveFormat = [Enum]::GetNames([microsoft.office.interop.word.WdSaveFormat])
$SaveFormat = $AllSaveFormat
$objDoc.SaveAs([ref]$SaveAsPathNew,[ref]$SaveFormat::wdFormatDocument) #Overwrite if exists
$objDoc.Close()
$objWord.Quit()
$null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject([System.__ComObject]$objWord)
[gc]::Collect()
[gc]::WaitForPendingFinalizers()
Remove-Variable objWord
If you have a way to do this in Word 2016 I'll take it too as I'll have to redo it all for office 2016 later on.
UPDATE: Looks like the solution is to create a "Range" where the is located. By default it seems you have one range[0] that represents the whole document.
The closest I came to do this was:
$MyRange = $objSelection.Range[Start:= 0, End:= 7]
But this is not the syntax for PowerShell and it deals only with absolute character positions and not the results of a search for a string.
If I could create a 2nd range maybe this could work:
$objSelection.Range[1].InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)
I can't write powershell script for you, but I can describe what you need. You are, indeed, on-track with the Range idea. To get the Range for an entire Word document:
$MyRange = $objDoc.Content
Note that you're free to declare and use as many Range objects as you need. Range is not limited like Selection, of which there can be only one. As long as you don't need the original Range again, go ahead and perform Find on it. If you do need the original Range again, then declare and instantiate a second, instantiating it either as above or, in order to use the original as the starting point:
$MyRange2 = $MyRange.Duplicate
Then use Find on a Range. Note that, when Find is successful the content of the Range is the found term, which puts the "focus" on the correct page. (But will not move the Selection!)
$MyRange.Find.Execute($FindText,$MatchCase,$MatchWholeWord,$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,$Wrap,$Format,$ReplaceWith,$wdReplaceAll)
If you want to test whether Find was successful the Execute method returns a Boolean value (true if Find was successful).
Use something like what follows to insert the file, although I'm not sure OLEObject is the best method for inserting a text file. I'd rather think InsertFile would be more appropriate. An OLEObject requires an OLE Server registered in Windows that supports editing text files; it will be slower and put a field code in the document that will try to update...
$result =$MyRange.InlineShapes.AddOLEObject($null,$TextFileObj.FullName,$false,$true)

Powershell mail merge only prints first page

I am using Powershell to script a mail merge using MS Word and the code below... The merge executes correctly, however when I use the .PrintOut() command, only the first page is printed.
Is there anyway I can force it to print all of the pages from the completed merge?
Thank you.
$word = New-Object -ComObject "Word.application"
$word.visible = 1
$doc = $word.Documents.Open("$PSScriptRoot\resources\templateFile.docx")
$doc.MailMerge.Execute()
$doc.PrintOut()
$quitFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveOptions],"wdDoNotSaveChanges")
$word.Quit([ref]$quitFormat)
Try specifying WdPrintOutRange as wdPrintAllDocument:
$doc.PrintOut(
[ref]$false,
[ref]$false,
[ref][Microsoft.Office.Interop.Word.WdPrintOutRange]::wdPrintAllDocument
)
Finally figured it out... My original code opened the "templateFile.docx" file and then executed the Mail Merge which actually creates a new document called "Form Letters1.docx"
Simply issuing the $doc.PrintOut() command will print the original (non-merged) "templateFile.docx" which is only a single page.
The solution is to switch "focus" to the newly created / merged document before printing.
Here is the working code:
$word = New-Object -ComObject "Word.application"
$word.visible = 1
$doc = $word.Documents.Open("$PSScriptRoot\templateFile.docx")
$doc.MailMerge.Execute()
($word.documents | ?{$_.Name -match "Letters1"}).PrintOut()
Start-Sleep -Seconds 5
$quitFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveOptions],"wdDoNotSaveChanges")
$word.Quit([ref]$quitFormat)
Another interesting point is the "Start-Sleep" command... without it, Word exits before it can get the merged document to the queue, resulting in nothing being printed.

Powershell prompt at bottom

I'd like to get my prompt in powershell to be at the bottom instead of "from top to bottom".
There is a workaround for cmd (https://superuser.com/questions/644326/start-conemu-with-prompt-at-the-bottom) but I can't find a way to make it work in powershell.
Does anyone have an idea?
Thanks a lot!
Thought, more "clean" version of prompt function. No need of New-Object ... Just add/modify your prompt in the $profile.
function prompt {
# put cursor at the bottom of the buffer
$rawUI = (Get-Host).UI.RawUI
$cp = $rawUI.CursorPosition
$cp.Y = $rawUI.BufferSize.Height - 1
$rawUI.CursorPosition = $cp
# and the prompt itself
Write-Host -NoNewline -ForegroundColor Cyan "PS "
Write-Host -NoNewline -ForegroundColor Yellow $(get-location).ProviderPath
return ">"
}
I'm not sure this is what you want, but this works fairly well for me. Save this as a .ps1 file somewhere that makes sense or convert it into a cmdlet if you like. Then stick it in your profile so it runs when you open a powershell session:
cls
$Ui = (Get-Host).UI.RawUI
$Height = $UI.WindowSize.Height
$Coordinates = New-Object System.Management.Automation.Host.Coordinates 0,($Height - 1)
$Ui.CursorPosition = $Coordinates
Create an alias for it.
New-Alias -Name cl -Value \\Path_to_the_script_or_the_cmdlet
Use the alias to clear your screen rather than clear or cls.

Installing System Font with Powershell

I have a folder filled with TTF files of custom fonts. I need to install them as system fonts using a powershell script (this is on Windows Server 2008 R2). Does anybody know how to do that in powershell?
Thanks!
It is quite simple. Take a look on the snippet below:
$FONTS = 0x14
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.Namespace($FONTS)
$objFolder.CopyHere("C:\test\Myfont.ttf")
And it should not require to restart/logoff...
The 0x14 value is the CLSID of the special folder.
In addition I just found this tutorial explaining each step above:
http://windowsitpro.com/scripting/trick-installing-fonts-vbscript-or-powershell-script
Just wanted to post an alternative which doesn't require 0x14 to be hard coded into the script. Pass the file object to the function, and it will just run the "Install" based on where the file is:
Function Install-Font {
Param (
[Parameter(Mandatory=$true,ValueFromPipeline=$true)][System.IO.FileSystemInfo[]]$File
)
$shell = New-Object -ComObject Shell.Application
$File | % {
$Fonts = $shell.NameSpace($_.Directory.Name)
$font = $Fonts.ParseName($_.Name)
$font.InvokeVerb("Install")
}
}
Using the Shell.Application COM object doesn't work on Server Core (at least not on 2012 R2).
I had success by simply copying the font file to C:\Windows\Fonts (in this case times.ttf) and then adding the corresponding registry entry with PowerShell:
New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts' -Name 'Times New Roman (TrueType)' -PropertyType String -Value times.ttf
Removal is the reverse of installation. The only downside is that a restart is required both after the font has been installed and also before it is uninstalled if an application has referenced it.
Shell code has been known to fail on Remote and Build agents - if the comobjects using shell are failing and you are vetting via Remote or Build agents then you will need to use the framework classes to do this (reference)
## Add or Remove Font Files - only tested with TTF font files thus far
#<#
#=======================================================================================================
# ADD or REMOVE MULTIPLE FONT FILES [Using ComObjects]
#=======================================================================================================
# This code will install or uninstall a font using ComObject
# You Must Modify the following variables in order to work
# (A) $dirFiles ==> This is the source folder path that contains all of your font files
# (B) $InstallOrUninstall ==> $true = Install Font ... $false = UnInstall Font
#=======================================================================================================
# Define Working Variables
$dirFiles = "C:\Temp\Fonts"
$InstallOrUninstall = $false # $true = Install = 1 ...or... $false = UnInstall = 0
$srcFontFiles = Get-ChildItem "$($dirFiles)\Fonts"
$Fonts = (New-Object -ComObject Shell.Application).Namespace(0x14)
# Copy each file into the Font Folder or Delete it - Depends on the $InstallOrUninstall variable setting
ForEach($srcFontFile in $srcFontFiles)
{
$srcFontFileName = $srcFontFile.name
$srcFontFileFullPath = $srcFontFile.fullname
$targFonts = "C:\Windows\Fonts\$($srcFontFileName)"
If (Test-Path $targFonts -PathType any) { Remove-Item $targFonts -Recurse -Force } # UnInstall Font
If ((-not(Test-Path $targFonts -PathType container)) -and ($InstallOrUninstall -eq $true)) { $fonts.CopyHere($srcFontFileFullPath, 16) } # Install Font
}
#>

How to pin to start menu using PowerShell

I can pin some programs to taskbar on Win7 using PowerShell.
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Tas&kbar'}
if ($verb) {$verb.DoIt()}
How do I modify the above code to pin a program to the Start menu?
Another way
$sa = new-object -c shell.application
$pn = $sa.namespace($env:windir).parsename('notepad.exe')
$pn.invokeverb('startpin')
Or unpin
$pn.invokeverb('startunpin')
Use the code below
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace('C:\Windows')
$item = $folder.Parsename('notepad.exe')
$verb = $item.Verbs() | ? {$_.Name -eq 'Pin to Start Men&u'}
if ($verb) {$verb.DoIt()}
Note: the change is in the fourth line.
The main problem with most of the solution is that they enumerate the verbs on a file, search for the string to perform the action (“Pin to Startmenu” etc.) and then execute it. This does not work if you need to support 30+ languages in your company, except you use external function to search for the localized command (see answer from shtako-verflow).
The answer from Steven Penny is the first that is language neutral and does not need any external code. It uses the verbs stored in the registry HKEY_CLASSES_ROOT\CLSID\{90AA3A4E-1CBA-4233-B8BB-535773D48449} and HKEY_CLASSES_ROOT\CLSID\{a2a9545d-a0c2-42b4-9708-a0b2badd77c8}
Based on this, here’s the code we are now using:
function PinToTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarpin"
}
function UnpinFromTaskbar {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "taskbarunpin"
}
function PinToStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startpin"
}
function UnpinFromStartmenu {
param([Parameter(Mandatory=$true)][string]$FilePath)
ExecuteVerb $FilePath "startunpin"
}
function ExecuteVerb {
param(
[Parameter(Mandatory=$true)][string]$File,
[Parameter(Mandatory=$true)][string]$Verb
)
$path = [System.Environment]::ExpandEnvironmentVariables($File)
$basePath = split-path $path -parent #retrieve only the path File=C:\Windows\notepad.exe -> C:\Windows
$targetFile = split-path $path -leaf #retrieve only the file File=C:\Windows\notepad.exe -> notepad.exe
$shell = new-object -com "Shell.Application"
$folder = $shell.Namespace($basePath)
if ($folder)
{
$item = $folder.Parsename($targetFile)
if ($item)
{
$item.invokeverb($Verb)
# "This method does not return a value." (http://msdn.microsoft.com/en-us/library/windows/desktop/bb787816%28v=vs.85%29.aspx)
# Therefore we have no chance to know if this was successful...
write-host "Method [$Verb] executed for [$path]"
}
else
{
write-host "Target file [$targetFile] not found, aborting"
}
}
else
{
write-host "Folder [$basePath] not found, aborting"
}
}
#PinToTaskbar "%WINDIR%\notepad.exe"
#UnpinFromTaskbar "%WINDIR%\notepad.exe"
PinToStartmenu "%WINDIR%\notepad.exe"
#UnpinFromStartmenu "%WINDIR%\notepad.exe"
See the script (international) here : http://gallery.technet.microsoft.com/scriptcenter/b66434f1-4b3f-4a94-8dc3-e406eb30b750
If you want to add an action like Pin to Modern UI interface (Windows 8), at $verbs, add 51201
Steven Penny's second answer above worked well for me. Here are a couple more tidbits.
It's doing COM through PowerShell, so you can do the same thing with pretty much any COM client. For example, here's an AutoHotkey version.
Shell := ComObjCreate("Shell.Application")
Target := Shell.Namespace(EnvGet("WinDir")).ParseName("Notepad.exe")
Target.InvokeVerb("startpin")
VBScript or InnoSetup would look almost the same except for the function used to create the object.
I also found that I have one program that pinned OK, but didn't have the right icon and/or description because of limitations in the compiler. I just made a little 1-line WinForms app that starts the target with Process.Start, and then added the appropriate icon, and the name I wanted in the Start Menu in the Title property in AppInfo.cs.