• RE: Moving Documents to another Cabinet with Indexes

    Simon,

    I'm still on 6.11 (on-premise), but are "Export Workflows" still a thing? Those let you move documents (with indexes) from a source to a destination.

    An Export Workflow is of the type of workflows that are built in without requiring the WorkFlow module nor the AutoIndex module.

    Search this PDF for "Document Transfer". It looks like that is the version 7 way of doing things...

    Thanks,
    Joe Kaufman
  • RE: (Using Platform Services) Can a document be downloaded WITH its stamps included?

    Danny,

    Glad you got things figured out! I am appreciative of your post because I realized using the straight HTTP calls from non-NET environments was not bringing down stamps and annotations. I had to utilize the longer URL to get those to come down, so now that works, too (in Visual FoxPro).

    You state, "STAMPS always come over with the document when downloaded from Platform Services in any setting."... That was not the case for me. The first thing I did when I started researching your question was stamp a document and draw some annotations on it, and neither came down when I pulled the file down (just using a straight stream to file). Only when I changed the target type to PDF and set KeepAnnotations to true did they come down. (This behavior was the same using .NET and Visual FoxPro.) I never had a scenario where just one or the other showed up -- it was both or neither in every scenario I tried. Granted, I stopped testing various combinations of properties once I was able to get stamps and annotations to pull down, but still...

    I think this thread should be helpful to future platform users downloading files!

    Thanks,
    Joe Kaufman



     
  • RE: (Using Platform Services) Can a document be downloaded WITH its stamps included?

    Danny,

    I think I figured it out...

    The comments for the DocuWare FileDownloadBase type helped:
     
    public abstract class FileDownloadBase
    {
        //
        // Summary:
        //     Creates a new instance of this class
        public FileDownloadBase();
    
        //
        // Summary:
        //     Specifies the annotation layers to be included in the output file.
        //
        // Remarks:
        //     This list is used only when KeepAnnotations flag is set to true; if this flag
        //     is set to false, then this list is ignored and no layers are included. In order
        //     to keep all layers, just set KeepAnnotations to true and pass an empty list or
        //     do not specify a list at all.
        [Dolphin]
        [XmlElement("Layers")]
        public List<int> Layers { get; set; }
        //
        // Summary:
        //     If this flag is true then the annotations are rendered in the PDF file, otherwise
        //     the annotations are removed.
        //
        // Remarks:
        //     This flag applies only to the PDF target format.
        [DefaultValue(true)]
        [XmlAttribute]
        public bool KeepAnnotations { get; set; }
    }

    So, the Layers property doesn't really matter, but KeepAnnotations only works for a target of PDF. I was using "Auto". When I switched it to be specifically "PDF", annotations and stamps downloaded with the file!

    This is what my download routine looks like now in C#:
     
    public static string SaveDocumentToFile(this Document document, string fileName = "")
    {
        LastError = "";
        try
        {
            if (document.FileDownloadRelationLink == null)
            {
                document = document.GetDocumentFromSelfRelation();
            }
    
            FileDownloadType targetFileType = FileDownloadType.Auto;
            if (document.ContentType.ToUpper() == "application/pdf".ToUpper())
            {
                // Be specific about PDF otherwise KeepAnnotations will not work as desired.
                targetFileType = FileDownloadType.PDF;
            }
    
            var downloadResponse = document.PostToFileDownloadRelationForStreamAsync(
                new FileDownload()
                {
                    TargetFileType = targetFileType,
                    KeepAnnotations = true
                }).Result;
    
            var contentHeaders = downloadResponse.ContentHeaders;
            string defaultFileName = contentHeaders.ContentDisposition.FileName;
            // If there is a space in the file name, the content header filename will place escaped double-quote marks around the
            // file name. This is a problem for other file-name manipulation routines.
            defaultFileName = defaultFileName.Replace('"', ' ').Trim();
            string defaultExtension = VFP.JUSTEXT(defaultFileName);
            if (fileName.Length == 0)
            {
                // No file name passed in, so we should get a default file name for saving the document.
                fileName = VFP.PUTFILE(defaultFileName, defaultExtension, "Save As");
            }
            // If file name is empty at this point, it indicates user cancellation.
            if (fileName.Length == 0) return "";
    
            using (FileStream file = File.Create(fileName))
            {
                using (Stream stream = downloadResponse.Content)
                {
                    stream.CopyTo(file);
                }
            }
            return fileName;
        }
        catch (Exception ex)
        {
            LastError = ex.Message;
            return "";
        }
    }

    Nevermind the "VFP" static function calls -- those are part of my VFP library that uses VFP function names to do things in native C#. The bottom line is that the file that gets pulled down specifically targets PDF if the content type of the document is in fact PDF, and then KeepAnnotations is honored properly. You don't need to set Layers to anything at all. You can also disregard "LastError" -- that is a static member field I use to set the last error if one occurs.

    Hope this helps!

    Thanks,
    Joe Kaufman

     
  • RE: DWIntegration EX_INVALID_PASS_PHRASE-1529591502

    Romain,

    Yes, your concerns are certainly valid! We are all internal, on-premise, so as long as username and password are casually hidden, that is good enough. We don't have anyone intercepting traffic and UrlDecoding to phish for passwords...

    I think I tried the fully encrypted way and never got it to work either, and not just because I was using FoxPro. I believe I tried in C# and couldn't get it working until I used the DocuWare DLL that provides the underpinnings for URL integration. I don't know how to take advantage of that through PHP unless PHP has a .NET bridge you can take advantage of. Using the .NET assembly DocuWare.WebIntegration.dll lets you create a URL via code such as:
     
    public static DWIntegrationUrl GetIntegrationUrl(IntegrationType integrationType = IntegrationType.Viewer)
    {
        LastError = "";
        DWIntegrationUrl returnUrl = null;
        try
        {
            // Prepare DWIntegration information.
            DWIntegrationInfo integrationInfo = new DWIntegrationInfo(ServiceEntryPoint + "/WebClient", DefaultOrganizationID, false);
            integrationInfo.Scheme = DefaultIntegrationProtocol;
            integrationInfo.OrganizationId = DefaultOrganizationID;
            // Now generate a plain-text URL object with the appropriate integration type.
            returnUrl = new DWIntegrationUrl(integrationInfo, integrationType);
            // Set up default user credentials. *** NOTE *** Password goes first, then username!
            returnUrl.Parameters.UserCredentials = new UserCredentials(DefaultIntegrationPassword, DefaultIntegrationUsername);
            // Set some other reasonable defaults for the integration URL object.
            returnUrl.Parameters.DisplayOneDoc = true;
        }
        catch (Exception ex)
        {
            LastError = ex.Message;
        }
        return returnUrl;
    }

    (LastError is a static member field that I use for error handling/communication.)

    Here are a couple deeper dives into what the DocuWare.WebIntegration assembly/namespace exposes:
     
    namespace DocuWare.WebIntegration
    {
        public class DWIntegrationUrl : DWWebUrl
        {
            public DWIntegrationUrl(DWIntegrationInfo integrationInfo, IntegrationType integrationType);
            public DWIntegrationUrl(DWIntegrationInfo integrationInfo, DWIntegrationUrlParameters urlParams);
    
            public DWIntegrationUrlParameters Parameters { get; set; }
            public DWIntegrationInfo Info { get; set; }
    
            public static DWIntegrationUrl Parse(string url);
            public static DWIntegrationUrl Parse(DWIntegrationInfo integrationInfo, string urlString);
            public static DWIntegrationUrl Parse(DWIntegrationInfo integrationInfo, Dictionary<string, string> parameters);
            public static DWIntegrationInfo ParseIntegrationInfo(string urlString);
        }
    
        public abstract class DWWebUrl
        {
            protected DWWebUrlLoginParameters parameters;
    
            protected DWWebUrl(DWWebLoginInfo integrationInfo, IntegrationType integrationType);
            protected DWWebUrl(DWWebLoginInfo integrationInfo, DWWebUrlLoginParameters urlParams);
    
            public string HttpAddress { get; }
            public string BaseUrl { get; }
            public virtual string Url { get; }
            public DWWebLoginInfo Info { get; set; }
            public DWWebUrlLoginParameters Parameters { get; set; }
            protected virtual string Page { get; }
        }
    
        public class DWIntegrationInfo : DWWebLoginInfo
        {
            public DWIntegrationInfo(string httpAddress, int orgID, bool useWindowsLogin);
            public DWIntegrationInfo(string httpAddress, string orgID, bool useWindowsLogin);
            protected DWIntegrationInfo(string httpAddress);
    
            public bool IsEncrypted { get; set; }
            protected override string LoginPage { get; }
    
            public static DWIntegrationInfo Parse(string urlString);
            protected override void AddQueryParams(Dictionary<string, string> par);
        }
    
        public class DWWebLoginInfo
        {
            protected bool isEncrypted;
    
            public DWWebLoginInfo(string httpAddress);
            public DWWebLoginInfo(string webClentUrl, int orgID, bool useWindowsLogin);
            protected DWWebLoginInfo(string webClentUrl, string orgID, bool useWindowsLogin);
    
            public bool IsEncrypted { get; }
            public string Scheme { get; set; }
            public string WebClientUrl { get; set; }
            public string OrganizationId { get; set; }
            public bool UseWindowsLogin { get; set; }
            public UrlVersion Version { get; set; }
            protected virtual string LoginPage { get; }
    
            public static DWWebLoginInfo Parse(string urlString);
            protected static bool ArrayContains(string[] array, string value);
            protected virtual void AddQueryParams(Dictionary<string, string> par);
            protected void ParseBaseUrl(string httpAddress);
            protected void ParseInternal(string urlString);
        }
    }

    I am not sure I would have gotten it to work any other way, as my URLs always looked a bit off from what the URL Creator was generating (as is the situation with you).

    Thanks,
    Joe Kaufman
  • RE: DWIntegration EX_INVALID_PASS_PHRASE-1529591502

    Romain,

    I can't be sure, but I think you might be encoding things too much, if that makes sense? Granted, I operate over an open connection, but this is how I do it (FoxPro code, hopefully it makes sense). This is what generating a URL to view a document might look like:
     
    FUNCTION DWViewDocument
        LPARAMETERS pcFilecabinetGUID, puDocumentID, plDontRunURL, plUseWindowsAuth
        IF (VARTYPE(pcFilecabinetGUID) <> "C") OR EMPTY(ALLTRIM(pcFilecabinetGUID))
            * We cannot proceed without a file cabinet GUID.
            RETURN ""
        ENDIF
        * Document ID can be passed in as a numeric value or a string.
        IF (VARTYPE(puDocumentID) == "C")
            puDocumentID = VAL(puDocumentID)
        ENDIF
        IF (VARTYPE(puDocumentID) <> "N")
            * We cannot proceed without a numeric document ID.
            RETURN ""
        ENDIF        
        SET PROCEDURE TO GlobalProc ADDITIVE
        LOCAL lcURL
        * Establish a base URL with a viewer as the integration type.
        lcURL = DWGetBaseIntegrationURL(DW_INTEGRATION_TYPE_VIEWER, plUseWindowsAuth)
        * Add on file cabinet GUID.
        lcURL = lcURL + "&fc=" + pcFilecabinetGUID
        * Add on the document ID.
        lcURL = lcURL + "&did=" + ALLTRIM(STR(INT(puDocumentID)))
        IF (NOT plDontRunURL)
            * Execute the URL.
            ExecuteCommand(lcURL)
        ENDIF
        * The URL used can be returned.
        RETURN lcURL
    ENDFUNC

    Here is what DWGetBaseIntegrationURL() looks like:
     
    FUNCTION DWGetBaseIntegrationURL
        LPARAMETERS pcIntegrationType, plUseWindowsAuth, plDontDisplayOneDoc
        IF (VARTYPE(pcIntegrationType) == "C")
            pcIntegrationType = UPPER(ALLTRIM(pcIntegrationType))
        ELSE
            * An empty integration type will make the URL be login-only.
            pcIntegrationType = ""
        ENDIF
        LOCAL lcBaseURL, lcParams
        * Start with our integration entry point.
        IF plUseWindowsAuth
            * Use NTML entry point.
            lcBaseURL = DW_INTEGRATION_ENTRY_POINT_NTLM
        ELSE
            lcBaseURL = DW_INTEGRATION_ENTRY_POINT
        ENDIF
        lcParams = ""
        * Now generate parameters, starting with login credentials (if we are not using a Windows login).
        IF (NOT plUseWindowsAuth)
            * We are NOT going use Windows authentication, so use login credentials generated here.
            lcParams = lcParams + "&lc=" + DWGetURLIntegrationCreds()
        ENDIF
        * We can set any other defaults here, too.
        IF (pcIntegrationType == DW_INTEGRATION_TYPE_RESULTLIST_VIEWER) OR (pcIntegrationType == DW_INTEGRATION_TYPE_HISTORY)
            IF (NOT plDontDisplayOneDoc)
                lcParams = lcParams + "&displayOneDoc=true"
            ENDIF
        ENDIF
        * Finally, add on the integration type parameter, if we have one.    
        IF (NOT EMPTY(pcIntegrationType))
            lcParams = lcParams + "&p=" + pcIntegrationType
        ENDIF
        IF (NOT EMPTY(lcParams))
            IF (LEFT(lcParams, 1) = "&")
                * Do not need the ampersand on the first parameter.
                lcParams = SUBSTR(lcParams, 2)
            ENDIF
            lcBaseURL = lcBaseURL + "?" + lcParams
        ENDIF
        RETURN lcBaseURL
    ENDFUNC

    And finally, here is what DWGetURLIntegrationCreds() looks like:
     
    FUNCTION DWGetURLIntegrationCreds
        SET PROCEDURE TO GlobalProc ADDITIVE
        LOCAL lcPlainCreds, lcEncodedCreds
        * Start with a plain-text credential string. We use a literal "\n" to denote a new-line character
        * between username and password, as that is how DocuWare expect the credentials to be encoded.
        lcPlainCreds = "User=" + DW_USERNAME_URL_INTEGRATION + "\nPwd=" + DW_PASSWORD_URL_INTEGRATION
        * URL encode the plain text into a login credential string.
        lcEncodedCreds = UrlTokenEncode(lcPlainCreds)
        RETURN lcEncodedCreds
    ENDFUNC

    The UrlTokenEncode() function does this (looks the same as yours):
     
    FUNCTION UrlTokenEncode
        * This function takes in a string and encodes it the same way System.Web.HttpServerUtility.UrlTokenEncode() 
        * would to in .NET:
        * 1. Use STRCONV to convert the input to Base64.
        * 2. Replace "+" with "-" and "/" with "_". Example: Foo+bar/=== becomes Foo-bar_===.
        * 3. Replace any number of "=" at the end of the string with an integer denoting how many they were. Example: Foo-bar_=== becomes Foo-bar_3.
        LPARAMETERS lcURL
        LOCAL lcEncoded, lnNumEqualSigns
        * 1. Convert string to Base64.
        lcEncoded = STRCONV(lcURL, 13)
        * 2. Replace certain characters with others.
        lcEncoded = CHRTRAN(lcEncoded, "+/", "-_")
        * 3. See how many "=" are at the end of the encoded string and convert to digit.
        lnNumEqualSigns = LEN(lcEncoded) - LEN(RTRIM(lcEncoded, 1, "="))
        lcEncoded = LEFT(lcEncoded, LEN(lcEncoded) - lnNumEqualSigns)
        lcEncoded = lcEncoded + ALLTRIM(STR(lnNumEqualSigns))
        RETURN lcEncoded
    ENDFUNC

    After all this, the URL is sent as-is -- no further encryption. User credentials and the query parameter are Url encoded, but that is it.

    Maybe that won't work if you are using a secure connection, but on my system what I generate matches the URL Integrator, and the URLs work to do things like view documents. Your user credentials look correct, so it must be something with encrypting/encoding.

    Good luck,
    Joe Kaufman
  • RE: View a document outside of DocuWare

    Seth,

    Well, someone is still going to need to log into DocuWare to use the Platform service. And, technically speaking, each user should be logging in to view the files they need, otherwise they could do an end-run around security. Licensing should be kosher because concurrent licenses will be used even if you use the same platform username from multiple workstations, and if you are on named users it does't really matter anyway because you've paid for it.

    But the permissions issue is a real one. If your Platform SDK code reveals documents that a user shouldn't have access to there is no way of knowing they are viewing a document from a file cabinet they aren't supposed to see. Logging them in with their own username via URL integration (or the Platform SDK) makes sure permissions are being honored for their specific credentials.

    To put it another way, beware "system" usernames that have access to everything, as you can introduce all sorts of problems usually taken care of by roles in DocuWare.

    Thanks,
    Joe Kaufman

     
  • RE: View a document outside of DocuWare

    Seth,

    Exactly right. You can download what you need and write as elaborate a client you want to view indexes and documents. It just takes work to write that client...

    If you really kinda sorta want to use the DocuWare viewer, why aren't you just using URL Integration?

    Thanks,
    Joe Kaufman
  • RE: View a document outside of DocuWare

    Seth,

    You would just use the API to download the file, then open it in Windows (the file extension would let Windows know what to open in). In C#, you just use Process.Start(<filename>) to open a file in its related application.

    If you also want to see the indexes, you would have to build an interface for that. As you access the Document object, that gets you the indexes, and then you can download the document itself via its download link.

    Here is the general Platform SDK documentation now:

    https://developer.docuware.com/dotNet/66b2ed1e-2aef-452a-97cd-5014bbf0242b.html

    and here is the .NET example of downloading a file:

    https://developer.docuware.com/dotNet_CodeExamples/4354370d-a0ce-42d4-ba94-6dc848ed62c0.html

    If you want to view a document without bringing DocuWare up in a browser yourself, you can use URL Integration.

    Thanks,
    Joe Kaufman
  • RE: Install DocuWare On-Premise, but in Azure Cloud

    Phil,

    Thanks for the explanation. Not sure I understand it (I thought things like Amazon VMs were almost entirely customizable and so could be configured like any local server, but you learn something new every day!

    You might want to amend the documentation to either list all the unsupported cloud VMs explicitly, just say "no cloud VMs", or provide details about why some cloud VMs don't work (e.g. "Cloud VMs that do not support protocol <whatever> are not supported by DocuWare.")

    Are VMs from Expedient and Rackspace supported, for example? What is the required IP protocol that is missing? Just curious...

    Thanks,
    Joe Kaufman
  • RE: Install DocuWare On-Premise, but in Azure Cloud

    And why are Azure VMs singled out as not supported? Does that mean AWS VMs are supported?

    A VM is a VM. We are running VM-based servers for our on-premise installation, so what is wrong with Azure VMs? Especially if DocuWare Cloud trusts Azure as its platform?

    Thanks,
    Joe Kaufman