locked
Custom Property for V4 Print Driver

    Question

  • I have a few questions about V4 print driver and Metro Style Device App.
    1. I want to add a custom property(Type: String) in my V4 driver, and user can change the value from Metro Device App.
       In my understanding, I should add the property to Print Ticket. Is that right?
       If so, how to implement this?  Could you give a sample about it?

    2. I want to check the input of that property.
       If user set the wrong value or never enter to my Device App, pop up a message or toast to cancel printing.
       How to implement it?

    Thanks in advance.

    Monday, June 11, 2012 5:56 PM

Answers

  • 1. There are details about the design of the JavaScript constraints in the Developing v4 Print Drivers whitepaper. It should answer all of those questions. The convertPrintTicketToDevmode and convertDevmodeToPrintTicket functions do require the use of the devmode property bag, since that is how you can persist the data across conversions.

    2. That is correct. Popping up dialogs is a user interaction pattern that doesn't fit in Metro, so v4 print drivers do not have the ability to do so. In your app, you can validate as users make choices. In the Metro print dialog, we will also do some validation and inform the user if they are constrained.

    3. That will break the layering and cause problems for you. Your app should never assume it knows more about the functionality of the driver than the driver itself knows. Metro style device apps are associated with the device, not the driver, so you should always make sure that your app is built to consume the data coming from the driver's PrintCapabilities. This ensures that the app doesn't tell the driver to do things it doesn't support.

    Tuesday, June 12, 2012 4:42 PM
  • Hi Georgezc,

    Sounds like you want to do something like a mandatory security PIN. This is do-able, but there are some considerations that will be apparent below.

    1. You can add a Parameter to your PrintCapabilities using the completePrintCapabilities function in JavaScript constraints. Then you'll need to make sure you handle the Parameter in the convertPrintTicketToDevmode and convertDevmodeToPrintTicket functions. Have a look at this sample to get started. http://code.msdn.microsoft.com/Print-driver-constraints-dcda532c

    2. When the user submits a print job, the PrintTicket will be validated and your validatePrintTicket function will be called. You can use this opportunity to fix the PrintTicket, or fail validation. You do need to consider that users may choose not to install your printer extension or Metro style device app, though, so if you implement a constraint on that parameter and the user does not have UI to change the PrintTicket, they can never print. So my advice would be to use the queue property bag to first configure whether the feature should be enforced (this requires an admin to take an explicit action to cause this locked down behavior). Then, if that property is set, you can fail validation unless the property is set. In your UI code, you should also validate before the user clicks the back button and be sure to point out that they should fill in the value before clicking back or OK.

    Hope this helps!

    Justin

    Monday, June 11, 2012 10:03 PM

All replies

  • Hi Georgezc,

    Sounds like you want to do something like a mandatory security PIN. This is do-able, but there are some considerations that will be apparent below.

    1. You can add a Parameter to your PrintCapabilities using the completePrintCapabilities function in JavaScript constraints. Then you'll need to make sure you handle the Parameter in the convertPrintTicketToDevmode and convertDevmodeToPrintTicket functions. Have a look at this sample to get started. http://code.msdn.microsoft.com/Print-driver-constraints-dcda532c

    2. When the user submits a print job, the PrintTicket will be validated and your validatePrintTicket function will be called. You can use this opportunity to fix the PrintTicket, or fail validation. You do need to consider that users may choose not to install your printer extension or Metro style device app, though, so if you implement a constraint on that parameter and the user does not have UI to change the PrintTicket, they can never print. So my advice would be to use the queue property bag to first configure whether the feature should be enforced (this requires an admin to take an explicit action to cause this locked down behavior). Then, if that property is set, you can fail validation unless the property is set. In your UI code, you should also validate before the user clicks the back button and be sure to point out that they should fill in the value before clicking back or OK.

    Hope this helps!

    Justin

    Monday, June 11, 2012 10:03 PM
  • Hi Justin,

    Thanks for your quickly reply.
    I understand what to do next. However, I still have a few questions about it.

    1. About JavaScript constraints:
        When will these functions be invoked? Do I need to add item in Devmode Property Bag?
        Do you mean that: 
            First completePrintCapabilities() will be invoked before Device App start, and I can add  Parameter to PrintCapabilities here. 
            Then, user can set the value in Device App, and the App save it to PrintTicket.
            However, when will convertPrintTicketToDevmode() and convertDevmodeToPrintTicket() be invoked? What should I do in these functions?
            And then, validatePrintTicket() will be invoked after user submits a print job.

    2. How to pop up error message if the validation is failed after print job submited?
         It seems no opportunity for my driver or App to pop up the messsage.

    3. Whether the following method can work?
        Not implement these in JavaScript constraints, but add a Parameter to PrintTicket directly in Device App when save.



    • Edited by Georgezc Tuesday, June 12, 2012 3:13 PM
    Tuesday, June 12, 2012 3:07 PM
  • 1. There are details about the design of the JavaScript constraints in the Developing v4 Print Drivers whitepaper. It should answer all of those questions. The convertPrintTicketToDevmode and convertDevmodeToPrintTicket functions do require the use of the devmode property bag, since that is how you can persist the data across conversions.

    2. That is correct. Popping up dialogs is a user interaction pattern that doesn't fit in Metro, so v4 print drivers do not have the ability to do so. In your app, you can validate as users make choices. In the Metro print dialog, we will also do some validation and inform the user if they are constrained.

    3. That will break the layering and cause problems for you. Your app should never assume it knows more about the functionality of the driver than the driver itself knows. Metro style device apps are associated with the device, not the driver, so you should always make sure that your app is built to consume the data coming from the driver's PrintCapabilities. This ensures that the app doesn't tell the driver to do things it doesn't support.

    Tuesday, June 12, 2012 4:42 PM
  • Hi Justin,

    I've tried just as you said.

    However, my Device App can't display with an error message occured, after I add the following Property to PrintCapabilities.

    <psf:PrintCapabilities ...>
      <psf:Property name="ns0000:PIN">
         <psf:Value xsi:type="xsd:string"/>
      </psf:Property>
    </psf:PrintCapabilities>

    During completePrintCapabilities(), the property is added to PrintCapabilities without any error.
    But after that function returns, the error happens.

    I also add the same Property to PrintTicket in convertDevmodeToPrintTicket(), it's just fine.
    What's wrong with that?


    • Edited by Georgezc Wednesday, June 13, 2012 3:14 PM
    Wednesday, June 13, 2012 3:08 PM
  • George, there's not enough information there for me to say what's happening. What error do you encounter? Does the PrintCapabilities XML pass PTConform?
    Wednesday, June 13, 2012 8:00 PM
  • Hi Justin,           

    I add the following source code in Constraint JS

    function completePrintCapabilities(printTicket, scriptContext, printCapabilities) {
       var currNode = printCapabilities.XmlNode.selectSingleNode("//psf:PrintCapabilities/psf:Property[@name='ns0000:PIN']");
       if (currNode == null) {
                 var currNode = printCapabilities.XmlNode.selectSingleNode("//psf:PrintCapabilities");
                 var newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", currNode.namespaceURI);
                 newPropertyNode.setAttribute("name", "ns0000:PIN");

                 newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
                 newValueNode.setAttribute("xsi:type", "xsd:string");
                 newPropertyNode.appendChild(newValueNode);

                 currNode.appendChild(newPropertyNode);
       }
    }

    The error message returns after I choose my V4 driver in Metro Print Dialog:
    "There was a problem connecting to the print. Make sure it's connected and try again."


    • Edited by Georgezc Friday, June 15, 2012 3:25 AM
    Friday, June 15, 2012 3:24 AM
  • George,

    You should hook up a debugger and see what's happening. Using a .NET test application and a debugger, I've noticed a few issues. Here's some stuff to get you started:

    1. Properties are not the right kind of mechanism to use here. You should use a ParameterDef (PrintCapabilities)/ParameterInit (PrintTicket) instead, since Properties are supposed to be static.

    2. Root level elements in a PrintTicket or PrintCapabilities document need to have a name that starts with Job, Document or Page.

    3. Be careful with namespaces. .NET is reporting an issue with how the xsi namespace was used in your new attribute. Try using setAttributeNS method instead of the normal setAttribute method.

    Thanks

    Justin

    Friday, June 15, 2012 6:02 PM
  • Justin,

    Thanks for your great support. I will try it to fix it.

    BTW, I use the debug method written in the write paper. However, I find nothing. 

    I'am a V3 GDI driver developer, and not so familiar with XPS driver. Maybe I still have questions in the future.



    • Edited by Georgezc Saturday, June 16, 2012 8:36 AM
    Saturday, June 16, 2012 8:34 AM
  • Hi Justin,

    I'm back.
    I tried to fix the problem as your comment, but I failed. I still met the problem as before.

    I upload my JavaScript constraints and DevMode Map XML.
    Please help me to check it.

    PS: I passed the check of PTConform for both PrintTicket and PrintCapabilities.

    Thanks.

    JavaScript Constraints:

     
    function validatePrintTicket(printTicket, scriptContext) {
        /// <param name="printTicket" type="IPrintSchemaTicket">
        ///     Print ticket to be validated.
        /// </param>
        /// <param name="scriptContext" type="IPrinterScriptContext">
        ///     Script context object.
        /// </param>
        /// <returns type="Number" integer="true">
        ///     Integer value indicating validation status.
        ///         retval 1 - Print ticket is valid and was not modified.
        ///         retval 2 - Print ticket was modified to make it valid.
        ///         retval 0 - Print ticket is invalid.
        /// </returns>
    
        var currNode = printTicket.XmlNode.selectSingleNode("//psf:PrintTicket/psf:ParameterInit[@name='ns0000:JobPIN']");
        if (currNode == null) {
            return 0;
        } 
    
        return 1;
        
    }
    
    function completePrintCapabilities(printTicket, scriptContext, printCapabilities) {
        /// <param name="printTicket" type="IPrintSchemaTicket" mayBeNull="true">
        ///     If not 'null', the print ticket's settings are used to customize the print capabilities.
        /// </param>
        /// <param name="scriptContext" type="IPrinterScriptContext">
        ///     Script context object.
        /// </param>
        /// <param name="printCapabilities" type="IPrintSchemaCapabilities">
        ///     Print capabilities object to be customized.
        /// </param>
        
        var currNode = printCapabilities.XmlNode.selectSingleNode("//psf:PrintCapabilities/psf:ParameterDef[@name='ns0000:JobPIN']");
        if (currNode == null) {
            var currNode = printCapabilities.XmlNode.selectSingleNode("//psf:PrintCapabilities");
            var newParameterDefNode = printCapabilities.XmlNode.createNode(1, "psf:ParameterDef", currNode.namespaceURI);
            newParameterDefNode.setAttribute("name", "ns0000:JobPIN");
    
            var newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:DataType");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
    
            newValueNode.setAttribute("xsi:type", "xsd:QName");
            newValueNode.text = "xsd:string";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:UnitType");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:string");
            newValueNode.text = "characters";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:DefaultValue");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:string");
            newValueNode.text = "";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:Mandatory");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:QName");
            newValueNode.text = "psk:Optional";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:MinLength");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:integer");
            newValueNode.text = "0";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            newPropertyNode = printCapabilities.XmlNode.createNode(1, "psf:Property", newParameterDefNode.namespaceURI);
            newPropertyNode.setAttribute("name", "psf:MaxLength");
            var newValueNode = printCapabilities.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:integer");
            newValueNode.text = "5";
            newPropertyNode.appendChild(newValueNode);
            newParameterDefNode.appendChild(newPropertyNode);
    
            currNode.appendChild(newParameterDefNode);
        }
        
    }
    
    function convertPrintTicketToDevMode(printTicket, scriptContext, devModeProperties) {
        /// <param name="printTicket" type="IPrintSchemaTicket">
        ///     Print ticket to be converted to DevMode.
        /// </param>
        /// <param name="scriptContext" type="IPrinterScriptContext">
        ///     Script context object.
        /// </param>
        /// <param name="devModeProperties" type="IPrinterScriptablePropertyBag">
        ///     The DevMode property bag.
        /// </param>    
       
        var currNode = printTicket.XmlNode.selectSingleNode("//psf:PrintTicket/psf:ParameterInit[@name='ns0000:JobPIN']");
        if (currNode != null) {
            var valueNode = currNode.selectSingleNode("psf:Value");
            devModeProperties.setString("JobPIN", valueNode.text);
        }    
    }
    
    
    function convertDevModeToPrintTicket(devModeProperties, scriptContext, printTicket) {
        /// <param name="devModeProperties" type="IPrinterScriptablePropertyBag">
        ///     The DevMode property bag.
        /// </param>
        /// <param name="scriptContext" type="IPrinterScriptContext">
        ///     Script context object.
        /// </param>
        /// <param name="printTicket" type="IPrintSchemaTicket">
        ///     Print ticket to be converted from the DevMode.
        /// </param>    
        
        var PIN = devModeProperties.getString("JobPIN");
        var currNode = printTicket.XmlNode.selectSingleNode("//psf:PrintTicket/psf:ParameterInit[@name='ns0000:JobPIN']");
        if (currNode == null) {
            var currNode = printTicket.XmlNode.selectSingleNode("//psf:PrintTicket");
            var newPropertyNode = printTicket.XmlNode.createNode(1, "psf:ParameterInit", currNode.namespaceURI);
            newPropertyNode.setAttribute("name", "ns0000:JobPIN");
    
            newValueNode = printTicket.XmlNode.createNode(1, "psf:Value", newPropertyNode.namespaceURI);
            newValueNode.setAttribute("xsi:type", "xsd:string");
            newValueNode.text = PIN;
            newPropertyNode.appendChild(newValueNode);
    
            currNode.appendChild(newPropertyNode);
        } else {
            var valueNode = currNode.selectSingleNode("psf:Value");
            valueNode.text = Prefix;
        }    
    }
    
    

    DevMode Map XML:

    <?xml version="1.0" encoding="utf-8"?>
    <Properties xmlns="http://schemas.microsoft.com/windows/2011/08/printing/devmodemap">
      <Property Name="JobPIN">
        <String Length="5">123</String>
      </Property>
    </Properties>

    Friday, June 29, 2012 6:24 AM
  • George,

    I can't debug your code any further (it just takes too much time to go into that level of detail via forums). The WDK support team is properly set up to help with these kind of issues though. Alternatively, you can walk through the steps below (copied for convenience). Its possible that no errors will be encountered in your JavaScript, but you may see that the GetPrintCapabilities method returns an error code as a result of the DOM returned by your JavaScript. So you'll need to really look at that carefully.

    1. Set debugging registry key.

      Key Name: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print

      Value Name: EnableJavaScriptDebugging

      Type: DWORD

      Value: 1

    2. Install Visual Studio 2011
    3. Create a print queue using the driver that has the constraints JavaScript.
    4. Set this print queue as the default.
    5. Start your test app or an app that prints and begin a scenario that will cause JavaScript constraints to be invoked. The app must call into the PrintTicket/PrintCapabilities APIs in order to break into the JavaScript constraints; older apps like Notepad do not call into these APIs, but the XPS Viewer app does. Microsoft recommends using a test app here, since the scenarios can be more easily isolated and reproduced. (note: I've had a lot of success using PowerShell to build a very quick test application here. Try the script below)
    6. At this time, the “Visual Studio Just-In-Time Debugger” will pop up saying “An unhandled exception occurred in <your app>”
    7. Launch a new instance of Visual Studio 2011
    8. Choose Debug, then Attach To Process
    9. In the Attach to Process dialog, ensure that Attach To: is set to Script code
    10. Now choose the test app or app printing and finally choose Attach
    11. Click on “Break All”
    12. Now go back to the “Visual Studio Just-In-Time Debugger” dialog and click “No”
    13. Visual Studio will break into the debugger at the location called by the current test. You may now debug the code normally.

    Script (provided as-is, no warranties)

     

    [void][reflection.assembly]::LoadWithPartialName("System.Printing")
    [void][reflection.assembly]::LoadWithPartialName("System.IO")
    [void][reflection.assembly]::LoadWithPartialName("System")


    #http://blogs.msdn.com/b/powershell/archive/2008/01/18/format-xml.aspx
    function Format-XML ([xml]$xml, $indent=2)
    {
        $StringWriter = New-Object System.IO.StringWriter
        $XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter
        $xmlWriter.Formatting = "indented"
        $xmlWriter.Indentation = $Indent
        $xml.WriteContentTo($XmlWriter)
        $XmlWriter.Flush()
        $StringWriter.Flush()
        Write-Output $StringWriter.ToString()
    }

    function getPrintCapsAsXml($q) {
        $stream = $q.GetPrintCapabilitiesAsXml();
        $txt = streamToString($stream);
        return Format-Xml $txt
       
    }

    function streamToString($stream) {
        $reader = new-object System.IO.StreamReader -argumentList $stream
        $str = ""
        while (($line = $reader.readLine()) -ne $null) {
            $str += $line;
        }
        return $str;
    }


    $server = new-object system.Printing.PrintServer
    getPrintCapsAsXml($server.GetPrintQueue("Microsoft XPS Document Writer"))

    Friday, June 29, 2012 5:58 PM
  • Hi Justin,

    I tried you script and found that "xsi" can't be found in namespace.
    However, setAttributeNS can't be used in my JS (seams no such method).

    Could you give me a little more help?

    Saturday, July 7, 2012 3:27 PM