Installing System Font with Powershell - 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
}
#>

Related

CopyHere method

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

Visual studio Build Task Issue for PowerShell inline task - Azure

I am running a vsts build inline PowerShell script task to create package for Azure cloud service. It works fine and create package file from my local machine, but when I try to run from VSTS PowerShell inline task it gives error :
##[error]Cannot find path ‘D:\a_tasks\InlinePowershell_31f040e5-e040-4336-878a-59a493355534\1.1.6\ServiceConfiguration.Cloud.Test.cscfg’ because it does not exist.
Here is my PowerShell inline script below, It fails on the following line:
Copy-Item $serviceConfigurationPath $packageOutDir
I really appreciate your help on this.
Thanks,
# This is the VSTS repository path
$workingDirectory = “$/DevCodeBase/ToolDevBranch1.33”
$webProjectName = “WebRole1”
$cloudProjectName = ‘ProjAzureDeployment’
$evv =’Test’
$cppack = ‘C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\bin\cspack.exe’
$solutionDir = [string]::Format(“{0}”, $workingDirectory)
$webDir = [string]::Format(“{0}\{1}”, $workingDirectory, $webProjectName)
$packageOutDir = [string]::Format(“{0}\{1}”, $workingDirectory, $cloudProjectName)
$rolePropertyFile = [string]::Format(“{0}\{1}\{2}”, $workingDirectory, $cloudProjectName, “roleproperties.txt”)
# Create Role Properties File – This property file specifies the .Net framework against which webrole is going to run.
New-Item $rolePropertyFile -Type file -Force -Value “TargetFrameWorkVersion=v4.5” | Out-Null
New-Item $packageOutDir -Type directory -Force | Out-Null
# CSPack command Definition
$serviceDefinitionPath = [string]::Format(“{0}\{1}\ServiceDefinition.csdef”, $solutionDir, $cloudProjectName)
if ($evv -eq “Test”){
$serviceConfigurationPath = “ServiceConfiguration.Cloud.Test.cscfg”
}
else
{
$serviceConfigurationPath = [string]::Format(“{0}\{1}\ServiceConfiguration.Cloud.cscfg”, $solutionDir, $cloudProjectName)
}
$serviceRole = [string]::Format(“/role:{0};{1}”, $webProjectName, $webDir)
$rolePropertiesFile = [string]::Format(“/rolePropertiesFile:{0};{1}”, $webProjectName, $rolePropertyFile)
$sites = [string]::Format(“/sites:{0};Web;{1}”, $webProjectName, $webDir)
$packageOutput = [string]::Format(“/out:{0}\{1}.cspkg”, $packageOutDir, $cloudProjectName)
# $packageOutput = [string]::Format(“{0}\{1}.cspkg”, $packageOutDir, $cloudProjectName)
Write-Host $packageOutput
Write-Host $serviceConfigurationPath
# Build CSPKG file
& “C:\Program Files\Microsoft SDKs\Azure\.NET SDK\v2.9\bin\cspack.exe” $serviceDefinitionPath $serviceRole $rolePropertiesFile $sites $packageOutput /useCtpPackageFormat | Out-Null
Write-Host $serviceDefinitionPath
Write-Host $serviceRole
Write-Host $rolePropertiesFile
Write-Host $sites
Write-Host $packageOutput
Write-Host ‘before copy’
# Copy configuration file
Copy-Item $serviceConfigurationPath $packageOutDir
# Remove Role Properties File
Remove-Item -Path $rolePropertyFile -Force | Out-Null
In the VSTS task you'll have to specify an absolute path, otherwise the script will look in the temporary directory created for your inline powershell script.
For instance, you could supply the path to the file as a parameter like
-filepath "$(System.DefaultWorkingDirectory)\Solution\config.json"
(For a list of the variables you can use, have a peek here)
If you want to keep using a relative path, you can move to a file based (ie non-inline) script and use a relative path to that.

How to use path as parameter?

I would like to have a screenshot tool in PS. Because I don't want to reinvent the wheel I searched and found a script at github (https://github.com/mikepruett3/psfetch), which I adapted for my needs.
Now I would like to change the behaviour - when the script is started with no parameter it should make a screenshot in the current directory. If the user enters a path (with -Path) the screenshot should be saved there.
My idea was to define (in my case) $Tarpath and redefine it when the option is given. How to do this?
Here is my actual script:
# PSFetch.ps1
# A Screenfetch writen in PowerShell
#
# -----------------------------------------------------------
# The Original Inspirations for CMDfetch:
# -----------------------------------------------------------
# screenFetch by KittyKatt
# https://github.com/KittyKatt/screenFetch
# A very nice screenshotting and information tool. For GNU/Linux (Almost all Major Distros Supported) *This has been ported to Windows, link below.*
#
# archey by djmelik
# https://github.com/djmelik/archey
# Another nice screenshotting and information tool. More hardware oriented than screenFetch. For GNU/Linux
# -----------------------------------------------------------
#
# DONE: Function to Take the Screenshot
Function Take-Screenshot {
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = "$PSScriptRoot"
)
PROCESS {
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null
# Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
$bounds = [Windows.Forms.SystemInformation]::VirtualScreen
# Check Path for Trailing BackSlashes
# $TarPath = $PSScriptRoot
if ( $TarPath.EndsWith("\") ) {
$TarPath = $TarPath.Substring(0,$Path.Length-1)
}
# Define The Target Path
$stamp = get-date -f MM-dd-yyyy_HH_mm_ss
$target = "$TarPath\screenshot-$stamp.png"
# Take the Screenshot
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
$bmp.Save($target)
$graphics.Dispose()
$bmp.Dispose()
}
}
# DONE: Fix support for Multiple Monitors
# FROM: Shay Levy's Response - http://stackoverflow.com/questions/7967699/get-screen-resolution-using-wmi-powershell-in-windows-7
$ScreenWidth = 0
$ScreenHeight = 0
Add-Type -AssemblyName System.Windows.Forms
$DisplayCount = [System.Windows.Forms.Screen]::AllScreens.Bounds.Count
$Bounds = [System.Windows.Forms.Screen]::AllScreens | Select-Object -ExpandProperty Bounds
$ScreenWidth = $Bounds | Measure-Object -Property Width -Sum | Select-Object -ExpandProperty Sum
$ScreenHeight = $Bounds | Measure-Object -Property Height -Maximum | Select-Object -ExpandProperty Maximum
$RESOLUTION = "$ScreenWidth x $ScreenHeight"
# Take Screenshot if the Parameters are assigned...
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath $target
edit
i forgot to remove the $tarpath int the PROCESS-block.
It remained here from my first tests...
OK, I solved it myself.
First of all [string]$TarPath = "$PSScriptRoot" doesn't work at all!
The variable is always empty.
However, my first idea was to define $TarPath and leave it unchanged until it defined again. This turned out that doesn't work.
Here is my solution:
# Define The Target Path
Write-Host "Please enter Screenshot-Path"
$TarPath = Read-Host "Else the screenshot will be in $PWD"
if (!$TarPath) {$TarPath = $pwd}
If nothing is entered at the prompt $pwd will be used.
Well, yes, that's about it, that approach will work. The only thing is that in the PROCESS block, you re-assign once again your $TarPath, making your fallback mechanism ineffective:
$TarPath = $PSScriptRoot
Delete that line and it will work like a charm.
Additionally, you could add validations such as making sure the parameter can be null, but not empty, and must be a valid path:
[ValidateScript({if ($_){ Test-Path $_}})]
[string]$TarPath = "$PSScriptRoot"
One last thing, if you want, as described in your question, to let the user use -Path on the call, you can also add an alias to your Param.
[Alias('Path')]
[ValidateScript({if ($_){ Test-Path $_}})]
[string]$Path = "$PSScriptRoot"
You redifine $TarPath in your function body:
$TarPath = $PSScriptRoot
This unconditionally supersedes any value previously assigned to the parameter. Remove the line and you can pass the parameter like this:
Take-Screenshot -TarPath 'C:\some\folder'
or omit the parameter to leave it at its default value ($PSScriptRoot).
I'd recommend to also change the line
$target = "$TarPath\screenshot-$stamp.png"
into this:
$target = Join-Path $TarPath "screenshot-$stamp.png"
so you don't need to fiddle around with trailing backslashes.
Function Take-Screenshot {
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = "$PSScriptRoot"
)
PROCESS {
[Reflection.Assembly]::LoadWithPartialName("System.Drawing") > $Null
# Changed how $bounds is calculated so that screen shots with multiple monitors that are offset work correctly
$bounds = [Windows.Forms.SystemInformation]::VirtualScreen
# Define The Target Path
$stamp = get-date -f MM-dd-yyyy_HH_mm_ss
$target = Join-Path $TarPath "screenshot-$stamp.png"
# Take the Screenshot
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
$bmp.Save($target)
$graphics.Dispose()
$bmp.Dispose()
}
}
Addendum: There are two scenarios where defining the default value for the parameter -TarPath as $TarPath = "$PSScriptRoot" doesn't work:
The parameter is defined as a parameter to the script (not to a function within the script) and the script is run from CMD:
powershell -File 'C:\path\to\script.ps1'
The script is run with PowerShell v2. The variable was only available in modules prior to PowerShell v3.
In both scenarios "$PScriptRoot" can be replaced with $PWD.Path:
[CmdletBinding()]
Param(
[string]$Width,
[string]$Height,
[string]$TarPath = $PWD.Path
)
You just need to modify the last line of the script with either of the following:
For default directory:
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight
For custom directory:
Take-Screenshot -Width $ScreenWidth -Height $ScreenHeight -TarPath "D:\Piyush\temp"
And comment out the following line in the PROCESS block as you don't want to override the custom directory path with the default one.
$TarPath = $PSScriptRoot

Using PowerShell to create and add class libraries to a solution

I'm trying to automate some of the tasks I perform on each web project. What I want is a PoSH script that I can run on a new solution that will:
load the solution contained in the scripts current directory.
create new projects for the loaded solution and add them to the solution.
create some classes and add them to each of the projects.
So far I have a simple script (shown below) that finds and opens the local solution file.
$scriptpath = $MyInvocation.MyCommand.Path
$dir = Split-Path $scriptpath
Set-Location $dir
$solution = Get-ChildItem | Where-Object{($_.Extension -eq ".sln")}
if ($solution.Count -eq 0)
{
"Please place this script in the folder containing you solution file."
break;
}
$dteObj = New-Object -ComObject "VisualStudio.DTE"
$dteObj.Solution.Open($solution.FullName)
How can I now create new projects and add them to the solution?
Check out http://studioshell.codeplex.com/ by Jim Christopher.
To create a project , call GetProjectTemplate, and then pass the returned template paths to AddFromTemplate.
Try this in Package Manager Console.
#Create a Console Project
$csTemplatePath = $dte.Solution.GetProjectTemplate("ConsoleApplication.zip", "CSharp")
$csPrjPath = "C:\\Projects\\SolutionName\\ConsoleApplication1"
$dte.Solution.AddFromTemplate($csTemplatePath, $csPrjPath, "ConsoleApplication1", 'false')
#Create a C# class
$itemPath = $dte.Solution.GetProjectItemTemplate("Class.zip", "CSharp")
$prj = Get-Project
$prjItem = $prj.ProjectItems.AddFromTemplate($itemPath, "Project.cs")

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.