We have run into a strange problem and it seems we are not the only ones (see note at bottom).
The problem is that we want to call ShellExecute with the shell verb printto. We are checking if it is registered and if so, start a process. The registered verbs can be retrieved in ProcessStartInfo.Verbs
.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = @"C:\test.jpg";
startInfo.Verb = "printto";
startInfo.Arguments = "MyPrinter";
startInfo.UseShellExecute = true;
if (!startInfo.Verbs.Contains("printto"))
throw new Exception("PrintTo is not supported!");
try
{
Process.Start(startInfo);
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1155)
{
Console.WriteLine("Somehow printto is NOT registered...");
}
When running Windows 10 with the Photos UWP app as default viewer, the console will print that a Win32Exception with code 1155 was raised, which means that the file type is not registered (for the given verb). If the (old) windows picture viewer is default, this works.
Please also note, that we are checking if the verb is registered and only call this if it is. It seems that Microsoft does something different here.
The big question is: Why do these two MS APIs do not play together anymore and how can we circumvent that?
Note: There is an old discussion with an answer that is not particularly correct, but also a slightly different problem description: Windows 8 blows error on c# process for printing pdf file, how?
I therefore decided to start a new question and hope that it is aligned with the SO principles.
The ProcessStartInfo.Verbs
property is somewhat broken as it does not consider the way how newer versions of Windows (Windows 8 and above afaik) retrieve the registered application. The property only checks the verbs that are registered for the ProgId defined under HKCR\.ext
(as can be seen in the reference source) and does not consider other places such as below the Registry key HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.ext
or some other places, e.g. defined via Policy.
The best way is to not rely on checking the Registry directly (as done by the ProcessStartInfo
class), but to use the appropriate Windows API function AssocQueryString
to retrieve the associated ProgId:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32;
class Program
{
private static void Main(string[] args)
{
string fileName = @"E:\Pictures\Sample.jpg";
string progId = AssocQueryString(AssocStr.ASSOCSTR_PROGID, fileName);
var verbs = GetVerbsByProgId(progId);
if (!verbs.Contains("printto"))
{
throw new Exception("PrintTo is not supported!");
}
}
private static string[] GetVerbsByProgId(string progId)
{
var verbs = new List<string>();
if (!string.IsNullOrEmpty(progId))
{
using (var key = Registry.ClassesRoot.OpenSubKey(progId + "\\shell"))
{
if (key != null)
{
var names = key.GetSubKeyNames();
verbs.AddRange(
names.Where(
name =>
string.Compare(
name,
"new",
StringComparison.OrdinalIgnoreCase)
!= 0));
}
}
}
return verbs.ToArray();
}
private static string AssocQueryString(AssocStr association, string extension)
{
uint length = 0;
uint ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, "printto", null, ref length);
if (ret != 1) //expected S_FALSE
{
throw new Win32Exception();
}
var sb = new StringBuilder((int)length);
ret = AssocQueryString(
AssocF.ASSOCF_NONE, association, extension, null, sb, ref length);
if (ret != 0) //expected S_OK
{
throw new Win32Exception();
}
return sb.ToString();
}
[DllImport("Shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern uint AssocQueryString(
AssocF flags,
AssocStr str,
string pszAssoc,
string pszExtra,
[Out] StringBuilder pszOut,
ref uint pcchOut);
[Flags]
private enum AssocF : uint
{
ASSOCF_NONE = 0x00000000,
ASSOCF_INIT_NOREMAPCLSID = 0x00000001,
ASSOCF_INIT_BYEXENAME = 0x00000002,
ASSOCF_OPEN_BYEXENAME = 0x00000002,
ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004,
ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008,
ASSOCF_NOUSERSETTINGS = 0x00000010,
ASSOCF_NOTRUNCATE = 0x00000020,
ASSOCF_VERIFY = 0x00000040,
ASSOCF_REMAPRUNDLL = 0x00000080,
ASSOCF_NOFIXUPS = 0x00000100,
ASSOCF_IGNOREBASECLASS = 0x00000200,
ASSOCF_INIT_IGNOREUNKNOWN = 0x00000400,
ASSOCF_INIT_FIXED_PROGID = 0x00000800,
ASSOCF_IS_PROTOCOL = 0x00001000,
ASSOCF_INIT_FOR_FILE = 0x00002000
}
private enum AssocStr
{
ASSOCSTR_COMMAND = 1,
ASSOCSTR_EXECUTABLE,
ASSOCSTR_FRIENDLYDOCNAME,
ASSOCSTR_FRIENDLYAPPNAME,
ASSOCSTR_NOOPEN,
ASSOCSTR_SHELLNEWVALUE,
ASSOCSTR_DDECOMMAND,
ASSOCSTR_DDEIFEXEC,
ASSOCSTR_DDEAPPLICATION,
ASSOCSTR_DDETOPIC,
ASSOCSTR_INFOTIP,
ASSOCSTR_QUICKTIP,
ASSOCSTR_TILEINFO,
ASSOCSTR_CONTENTTYPE,
ASSOCSTR_DEFAULTICON,
ASSOCSTR_SHELLEXTENSION,
ASSOCSTR_DROPTARGET,
ASSOCSTR_DELEGATEEXECUTE,
ASSOCSTR_SUPPORTED_URI_PROTOCOLS,
ASSOCSTR_PROGID,
ASSOCSTR_APPID,
ASSOCSTR_APPPUBLISHER,
ASSOCSTR_APPICONREFERENCE,
ASSOCSTR_MAX
}
}
However, this does not solve your actual problem, i.e. printing an image on Windows 10. If your requirement is to just get the image printed out you can do so using the PrintDocument
class from the System.Drawing.Printing
namespace as described in this related post: Print images in C#:
PrintDocument pd = new PrintDocument();
pd.PrintPage += PrintPage;
pd.Print();
private void PrintPage(object o, PrintPageEventArgs e)
{
System.Drawing.Image img = System.Drawing.Image.FromFile("D:\\Foto.jpg");
Point loc = new Point(100, 100);
e.Graphics.DrawImage(img, loc);
}