This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Unable to Create New File in Workflow from a Template

I'm trying to create a new document from a template when another object enters a specific workflow state.  I couldn't find a way to easily create the document from the template so I am trying to use the information on copying a file on the following page:

https://developer.m-files.com/Samples-And-Libraries/Samples/Processes/Copying-Objects/

The code below performs the following actions:

  1. Finds the template.
  2. Copies the properties.
  3. Removes unnecessary properties. I had a shorter list but I removed all the unnecessary ones trying to troubleshoot this issue.
  4. Change some of the remaining properties.
  5. Copy the files in the template to a new SourceObjectFiles object.
  6. Rename the file. There's only one in this example.
  7. Copy the ACL.
  8. Create the new object/file.
  9. Delete the temporary files from the server.

Every step goes through without errors.  I've verified all the objects that I'm passing to the CreateNewObjectExQuick have good data in them.  The function returns an ID for the newly created document.  However, nothing is actually created.  I cannot see anything in M-Files and the logs don't show that anything has been created.

The documentation for the CreateNewObjectExQuick function state "if the source file(s) cannot be uploaded the newly created object is left checked out and cannot be accessed from the M-Files UI".  This is the only clue that I have as to why it may not be working.  However, I have verified all the data in the SourceObjectFiles object is accurate and pointing to the correct files in the Temp folder.  M-Files is able to access the folder because I see the file created then removed.  I have even tried the original CreateNewObject function, then checked in the object, with the same results.

I'm at a loss.  Does anyone have any ideas?

OPTION EXPLICIT

'# Define M-Files Property IDs #
DIM ARCHIVE_STATUS_ID : ARCHIVE_STATUS_ID = Vault.PropertyDefOperations.GetPropertyDefIDByAlias("PD.ArchiveStatus")
DIM DOCTYPE_ID : DOCTYPE_ID = Vault.PropertyDefOperations.GetPropertyDefIDByAlias("PD.DocumentType")
DIM JOB_NUMBER_ID : JOB_NUMBER_ID = Vault.PropertyDefOperations.GetPropertyDefIDByAlias("JobNumber")
DIM SALES_ORDER_ID : SALES_ORDER_ID = Vault.PropertyDefOperations.GetPropertyDefIDByAlias("SalesOrder")

'# Define M-Files Class IDs #
DIM ELECDOC_CLASS : ELECDOC_CLASS = Vault.ClassOperations.GetObjectClassIDByAlias("CL.ElectricalDocuments")

'# Create Archive Status Array #
Dim oArchiveStatus: Set oArchiveStatus = CreateObject("System.Collections.ArrayList")
oArchiveStatus.Add 57 '# Current #
'# Create Document Type Array #
Dim oDocumentType: Set oDocumentType = CreateObject("System.Collections.ArrayList")
oDocumentType.Add 174 '# Control Panel Checklist #

'# Create Search Conditions Object #
Dim oSearchConditions : Set oSearchConditions = CreateObject("MFilesAPI.SearchConditions")
'# Exclude Deleted Items #
oSearchConditions.Add -1, AddStatusSearchCondition(MFStatusTypeDeleted, MFConditionTypeEqual, MFDatatypeBoolean, False)
'# Add Electrical Documents Class to Search Conditions #
oSearchConditions.Add -1, AddPropertySearchCondition(MFBuiltInPropertyDefClass, MFConditionTypeEqual, MFDatatypeMultiSelectLookup, ELECDOC_CLASS)
'# Add Document Type Property to Search Conditions #
oSearchConditions.Add -1, AddPropertySearchCondition(DOCTYPE_ID, MFConditionTypeEqual, MFDatatypeMultiSelectLookup, oDocumentType)
'# Add Archive Status Property to Search Conditions #
oSearchConditions.Add -1, AddPropertySearchCondition(ARCHIVE_STATUS_ID, MFConditionTypeEqual, MFDatatypeMultiSelectLookup, oArchiveStatus)
'# Add IsTemplate Property to Search Conditions #
oSearchConditions.Add -1, AddPropertySearchCondition(MFBuiltInPropertyDefIsTemplate, MFConditionTypeEqual, MFDatatypeBoolean, True)

'# Get Search Results #
Dim oSearchResults : Set oSearchResults = Vault.ObjectSearchOperations.SearchForObjectsByConditions(oSearchConditions, 0, False)

If oSearchResults.Count = 0 Then
	Err.Raise MFScriptCancel, "Could not find the Control Panel Checklist Template."
ElseIf oSearchResults.Count > 1 Then
	Err.Raise MFScriptCancel, "Too many Control Panel Checklist Templates found. Only one template should be marked as 'Current'."
Else
	'# Found Checklist Template. Create New Checklist from Template. #
	Dim oChecklistTemplateObjVer : Set oChecklistTemplateObjVer = oSearchResults.Item(1).ObjVer
	Dim oChecklistTemplateProperties : Set oChecklistTemplateProperties = Vault.ObjectPropertyOperations.GetProperties(oChecklistTemplateObjVer, True)
	
	'# Copy Template Properties then Remove Unwanted Properties #
	Dim oNewChecklistProperties : Set oNewChecklistProperties = oChecklistTemplateProperties.Clone
	RemovePropertyValue oNewChecklistProperties, ARCHIVE_STATUS_ID
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefAccessedByMe
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefCreated
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefCreatedBy
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefIsTemplate
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefLastModified
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefLastModifiedBy
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefMarkedForArchiving
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefObjectChanged
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefOriginalPath
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefSizeOnServerThisVersion
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefSizeOnServerAllVersions
	RemovePropertyValue oNewChecklistProperties, MFBuiltInPropertyDefStatusChanged
	
	'# Set Property Metadata Fields #
	oNewChecklistProperties.SearchForProperty(JOB_NUMBER_ID).CloneFrom(PropertyValues.SearchForProperty(JOB_NUMBER_ID))
	oNewChecklistProperties.SearchForProperty(MFBuiltInPropertyDefNameOrTitle).Value.SetValue MFDatatypeText, "Test Checklist Document"
	oNewChecklistProperties.SearchForProperty(SALES_ORDER_ID).CloneFrom(PropertyValues.SearchForProperty(SALES_ORDER_ID))

	'# Copy Checklist Template Files #
	Dim oNewChecklistFiles : Set oNewChecklistFiles = GetObjectSourceFiles(oChecklistTemplateObjVer)
	oNewChecklistFiles.Item(1).Title = "Test Checklist Document"

	'# Copy Access Control List #
	Dim oAccessControlList : Set oAccessControlList = Vault.ObjectOperations.GetObjectPermissions(oChecklistTemplateObjVer).AccessControlList.Clone

	'# Create New Template in M-Files #
	Dim bSingleFileDocument : bSingleFileDocument = oNewChecklistProperties.SearchForProperty(MFBuiltInPropertyDefSingleFileObject).Value.Value
	Dim lNewID : lNewID = Vault.ObjectOperations.CreateNewObjectExQuick(oChecklistTemplateObjVer.Type, oNewChecklistProperties, oNewChecklistFiles, bSingleFileDocument, True, oAccessControlList)

	'# Delete Temporary Files #
	ClearTemporaryFiles(oNewChecklistFiles)
End If

Private Function AddPropertySearchCondition(iMFPropertyDef, iMFConditionType, iMFDataType, oSearchValue)
	'#################################################
	'# Purpose: Creates a SearchConditionEx object
	'#
	'# Parameters:
	'#   iMFPropertyDef - M-Files Property ID
	'#   iMFConditionType - M-Files MFConditionType Enumeration
	'#   iMFDataType - M-Files MFDataType Enumeration
	'#   oSearchValue - Value or Array of Values
	'#################################################
	Dim oSearchCondition : Set oSearchCondition = CreateObject("MFilesAPI.SearchCondition")

	oSearchCondition.Expression.SetPropertyValueExpression iMFPropertyDef, MFParentChildBehaviorNone, Nothing
	oSearchCondition.ConditionType = iMFConditionType
	If VarType(oSearchValue) <> 9 Then
	    '# oSearchValue is not an Array #
		oSearchCondition.TypedValue.SetValue iMFDataType, oSearchValue
	Else
		oSearchCondition.TypedValue.SetValue iMFDataType, oSearchValue.ToArray()
	End If

	Set AddPropertySearchCondition = oSearchCondition
End Function

Private Function AddStatusSearchCondition(iMFPropertyDef, iMFConditionType, iMFDataType, oSearchValue)
	'#################################################
	'# Purpose: Creates a SearchConditionEx object
	'#
	'# Parameters:
	'#   iMFPropertyDef - M-Files Property ID
	'#   iMFConditionType - M-Files MFConditionType Enumeration
	'#   iMFDataType - M-Files MFDataType Enumeration
	'#   oSearchValue - Value or Array of Values
	'#################################################
	Dim oSearchCondition : Set oSearchCondition = CreateObject("MFilesAPI.SearchCondition")

	oSearchCondition.Expression.SetStatusValueExpression iMFPropertyDef, Nothing
	oSearchCondition.ConditionType = iMFConditionType
	oSearchCondition.TypedValue.SetValue iMFDataType, oSearchValue

	Set AddStatusSearchCondition = oSearchCondition
End Function

Private Function GetObjectSourceFiles(oSourceObjVer)
	Dim oFSO: Set oFSO = CreateObject("Scripting.FileSystemObject")
	
	'# Get the Temporary Folder Path #
	CONST TEMPFOLDER = 2 '# https://msdn.microsoft.com/en-us/library/a72y2t1c(v=vs.84).aspx #
	Dim sTempFolderPath: sTempFolderPath = oFSO.GetSpecialFolder(TEMPFOLDER).Path
	
	'# Get Files for Current ObjVer Object #
	Dim oFiles: Set oFiles = Vault.ObjectFileOperations.GetFiles(oSourceObjVer)
	
	' Create New SourceObjectFiles Collection #
	Dim oSourceFiles: Set oSourceFiles = CreateObject("MFilesAPI.SourceObjectFiles")
	
	'# Add Files to the New SourceObjectFiles Collection #
	Dim iCounter
	Dim oFile

	For iCounter = 1 To oFiles.Count
		Set oFile = oFiles.Item(iCounter)

		'# Download Current File to Temp Folder #
		Dim sTempFilePath: sTempFilePath = oFSO.BuildPath(sTempFolderPath, oFSO.GetTempName()) & "." & oFile.Extension
		Vault.ObjectFileOperations.DownloadFile oFile.ID, oFile.Version, sTempFilePath

		'# Create ObjectSourceFile for Current File #
		Dim oObjectSourceFile: Set oObjectSourceFile = CreateObject("MFilesAPI.SourceObjectFile")
		oObjectSourceFile.Extension = oFile.Extension
		oObjectSourceFile.SourceFilePath = sTempFilePath
		oObjectSourceFile.Title = oFile.Title
		
		'# Add ObjectSourceFile to ObjectSourceFiles Collection #
		oSourceFiles.Add -1, oObjectSourceFile
	Next

	'# Return ObjectSourceFiles Collection #
	Set GetObjectSourceFiles = oSourceFiles
End Function

Private Sub ClearTemporaryFiles(oObjectSourceFiles)
	Dim oFSO: Set oFSO = CreateObject("Scripting.FileSystemObject")

	'# Delete the Files in the ObjectSourceFiles Collection from the Temp Folder #
	Dim iCounter, oFile
	For iCounter = 1 To oObjectSourceFiles.Count
		Set oFile = oObjectSourceFiles.Item(iCounter)
		If oFSO.FileExists(oFile.SourceFilePath) Then
			On Error Resume Next
			oFSO.DeleteFile oFile.SourceFilePath, True
			On Error Goto 0
		End If
	Next
End Sub

Private Sub RemovePropertyValue(ByRef oPropertyValues, iPropertyDef)
	'# Find Index of the Property Value (-1 if Not Found) #
	Dim iIndex: iIndex = oPropertyValues.IndexOf(iPropertyDef)
	If iIndex > -1 Then oPropertyValues.Remove iIndex
End Sub

  • If you get a value in "lNewID" then the system managed to successfully add the object.  I can only think of two options:

    • The transaction is being rolled back.  The object is created using a transactional vault reference so, if the transaction is rolled back then the object will be un-created.
    • The object is created but is not visible for some reason.  Have you tried searching as an admin, for example?
  • Hi Craig,

    Thank you for the reply. Everything you said makes sense, and is pretty much what I was thinking. The problem is that there's no information on why the transaction is not finalizing. I have searched as an admin and could not find the file. In addition, the vault log does not show that an object was created. It's very frustrating.

  • I don't have a magic answer, unfortunately.  If the transaction were rolled back then you should see some sort of error in the client.  Anything in the (server) Windows Event Logs?  What is running this code?  Is it in a state action directly?  Does the source object end up successfully in the state?

    Might be worth seeing whether your reseller or account manager can get someone to look at this with you.  I'm struggling to think of guidance I can give you remotely.

  • The code is in a 'Test' state that simply runs the VBScript.  Great question about whether or not it was getting TO that state!  I kept stopping the state transition at the end of my code with an Err.Raise command, which was causing the transaction to be rolled back!  I haven't used transactional commands much so it didn't even dawn on me that raising the error to report back information was causing the problem.

    After removing the Err.Raise command, the object finished the state transition and the new file was created successfully.  Sometimes it's the simple things that catch you. Slight smile

    Thanks for your help Craig!  Hopefully this thread will help someone else in the future.  I love M-Files but their API documentation can be lacking in detail and examples. Smiley

  • Aha, yes, raising an exception will cause the transaction to not be committed!

    Whilst the actual API documentation is not something I directly maintain, I do maintain the Developer Portal (https://developer.m-files.com).  If you feel there are topics that would be useful to cover in more depth over there then please do drop me a private message and we can talk through it.

    I can't guarantee that I can write the articles overnight, but maybe I can add them to the longer-term plans.

  • Oh! I didn't realize you were at M-Files. :D The API documentation is adequate but I find myself having to experiment a lot to figure out the nuances of the commands.  I will have to go to the developer portal more often to look around for information.  I haven't been there in a while because it seemed like it was being catered to VARs and commercial developers and had some things locked away from end users.  I did see that it seems to be opening up a bit more.

    For this particular use case, I was a bit surprised that it was so difficult to create a new file from a template.  I would have thought there would be a built-in function for performing this operation since it's so simple creating a file from a template in the UI.

    I really appreciate the offer to listen to my thoughts on potential topics.  I will certainly take you up on that!

    Thank you!!

  • Yes, I'm the Product Manager of our APIs and developer tools.  So I'm definitely interested in hearing your feedback!

    Historically perhaps that was the case, but we've been moving forward trying to rip down some of those barriers and enable developers to customise M-Files, regardless of whether they are at a reseller or customer or something else!

    One thing I would maybe highlight is that VBScript is cumbersome.  You may want to consider moving towards something like the Vault Application Framework.  Not only does the VAF have some helpers itself, there is also the community-driven "VAF Extensions" library which...  does have a helper method for copying objects.  Wink

    And please do take me up on that offer!

  • The VAF framework was one of the things that was locked away so I never got into it.  I did see that the new templates are available so I will definitely look into moving in that direction for future development. :D