A SOLIDWORKS user on the 3DEXPERIENCE forum recently posted a batch macro that failed to compile with “Invalid Character.” The macro tried to parameterize a Task Scheduler run with syntax like $$$FOLDER_PATH$$$ and made API calls to CustomPropertyData. Josh Brady identified it immediately: “That is AI-generated code. CustomPropertyData is a hallucinated call. It does not exist in the SolidWorks API.”

This failure pattern is now common enough to deserve a direct explanation. SOLIDWORKS Task Scheduler has no built-in parameter syntax. VBA macros cannot receive command-line arguments. There are no token substitutions in macro source code. Here is what works instead.

How Task Scheduler Actually Runs Macros

Before the fix, the problem needs to be clear.

Task Scheduler supports two different task modes that affect how macros behave:

Process Documents mode — Task Scheduler opens each file from a specified folder or file list, runs your macro on the active document, then closes the file. The macro operates on swApp.ActiveDoc. The folder is specified in the Task Scheduler UI, not in the macro. If your goal is to process every part in a folder, this is the correct mode. You do not need to pass a folder path to the macro at all — Task Scheduler handles the iteration.

Run Macro mode — Task Scheduler starts SolidWorks and runs a single macro once. The macro is responsible for opening its own files and managing file I/O. This is the mode where folder path becomes a real question: the macro must somehow know which folder to target.

Most engineers who ask “how do I pass a folder path to my macro?” are actually using Run Macro mode when Process Documents mode would solve their problem with no code changes. Before implementing any parameter-passing scheme, verify which mode fits your workflow.

Why AI Code Fails Here

The $$$FOLDER_PATH$$$ token notation does not exist in SolidWorks VBA. Depending on which AI model generated the code, it may have confused:

  • SolidWorks PDM data card variable syntax — PDM card design uses $PRP:"variablename" and %variablename% in different contexts, but none of these tokens appear in VBA source code
  • SolidWorks Drawing note property links — $PRPSHEET:"Description" syntax pulls custom properties into drawing annotations, but again, not in VBA
  • Other automation frameworks — Windows batch file %VARIABLE%, PowerShell $env:VAR, GitHub Actions ${{ inputs.var }}, and dozens of other systems use similar delimiters; training data from these leaks into generated SolidWorks code

CustomPropertyData is similarly fictional. The actual interfaces for custom properties in the SolidWorks API are:

  • IModelDocExtension.CustomPropertyManager — returns an ICustomPropertyManager object for a configuration
  • ICustomPropertyManager.Get6() — reads a property value
  • ICustomPropertyManager.Add3() — writes a property value
  • ICustomPropertyManager.Delete2() — removes a property

If you need to work with custom properties in bulk, the post on what the macro recorder silently drops covers why the recorder fails to capture property operations and what to write manually.

Pattern 1: Config File

The simplest reliable approach for Run Macro mode is a plain text config file. The macro reads the first line of a known file path at startup.

Option Explicit

' Path to the config file — hardcode this once and don't change it
Private Const CONFIG_FILE As String = "C:\CadTools\task_config.txt"

Sub main()
    Dim swApp As SldWorks.SldWorks
    Set swApp = Application.SldWorks

    Dim folderPath As String
    folderPath = ReadConfig(CONFIG_FILE, "FolderPath")
    
    If Len(folderPath) = 0 Then
        swApp.SendMsgToUser2 "Config key 'FolderPath' not found in: " & CONFIG_FILE, _
            swMessageBoxIcon_e.swMbWarning, swMessageBoxBtn_e.swMbOk
        Exit Sub
    End If
    
    ProcessFolder swApp, folderPath
End Sub

' Reads key=value pairs from a plain text config file.
' Returns empty string if file or key is missing.
Private Function ReadConfig(filePath As String, keyName As String) As String
    ReadConfig = ""
    
    If Dir(filePath) = "" Then Exit Function
    
    Dim fileNum As Integer
    fileNum = FreeFile()
    
    Open filePath For Input As #fileNum
    
    Dim line As String
    Do While Not EOF(fileNum)
        Line Input #fileNum, line
        line = Trim(line)
        
        ' Skip blank lines and comments
        If Len(line) = 0 Then GoTo NextLine
        If Left(line, 1) = "'" Then GoTo NextLine
        If Left(line, 1) = "#" Then GoTo NextLine
        
        Dim eqPos As Integer
        eqPos = InStr(line, "=")
        If eqPos > 0 Then
            Dim k As String, v As String
            k = Trim(Left(line, eqPos - 1))
            v = Trim(Mid(line, eqPos + 1))
            If LCase(k) = LCase(keyName) Then
                ReadConfig = v
                Close #fileNum
                Exit Function
            End If
        End If
        
NextLine:
    Loop
    
    Close #fileNum
End Function

Sub ProcessFolder(swApp As SldWorks.SldWorks, folderPath As String)
    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    If Not fso.FolderExists(folderPath) Then
        swApp.SendMsgToUser2 "Folder not found: " & folderPath, _
            swMessageBoxIcon_e.swMbWarning, swMessageBoxBtn_e.swMbOk
        Exit Sub
    End If
    
    Dim folder As Object
    Set folder = fso.GetFolder(folderPath)
    
    Dim f As Object
    For Each f In folder.Files
        Dim ext As String
        ext = LCase(Right(f.Name, 7))
        If ext = ".sldprt" Or ext = ".sldasm" Or ext = ".slddrw" Then
            ProcessFile swApp, f.Path
        End If
    Next f
End Sub

Sub ProcessFile(swApp As SldWorks.SldWorks, filePath As String)
    Dim errors As Long, warnings As Long
    Dim swDoc As SldWorks.ModelDoc2
    
    Set swDoc = swApp.OpenDoc6(filePath, _
        swDocumentTypes_e.swDocPART, _
        swOpenDocOptions_e.swOpenDocOptions_Silent, _
        "", errors, warnings)
    
    If swDoc Is Nothing Then Exit Sub
    
    ' --- your per-file work here ---
    
    swDoc.Save3 swSaveAsOptions_e.swSaveAsOptions_Silent, errors, warnings
    swApp.CloseDoc filePath
End Sub

The config file C:\CadTools\task_config.txt contains:

# SOLIDWORKS task scheduler config
FolderPath = C:\Projects\Assemblies\2026\Batch1
OutputPath = C:\Output\DXF

To change which folder the task processes, edit the config file. The macro and the Task Scheduler task definition stay unchanged. Multiple tasks can point to the same macro with different config files by creating task_a_config.txt, task_b_config.txt, and so on, and using a wrapper macro per task that calls ReadConfig with the appropriate path.

Pattern 2: Windows Registry

If you prefer not to manage text files, the Windows registry works cleanly for string parameters.

Option Explicit

Private Const REG_ROOT As String = "HKCU\Software\CadTools\SWTasks\"

Sub main()
    Dim swApp As SldWorks.SldWorks
    Set swApp = Application.SldWorks
    
    Dim wsh As Object
    Set wsh = CreateObject("WScript.Shell")
    
    Dim folderPath As String
    On Error Resume Next
    folderPath = wsh.RegRead(REG_ROOT & "FolderPath")
    On Error GoTo 0
    
    If Len(Trim(folderPath)) = 0 Then
        swApp.SendMsgToUser2 "Registry key not found: " & REG_ROOT & "FolderPath", _
            swMessageBoxIcon_e.swMbWarning, swMessageBoxBtn_e.swMbOk
        Exit Sub
    End If
    
    ProcessFolder swApp, folderPath
End Sub

Set the registry value from PowerShell before the task runs:

Set-ItemProperty -Path "HKCU:\Software\CadTools\SWTasks" `
    -Name "FolderPath" -Value "C:\Projects\Batch1" -Type String

Or from a scheduled PowerShell wrapper that writes the key, then invokes sldtaskscheduler.exe.

Process Documents Mode: The Folder Path You Don’t Need to Pass

If your goal is “run this macro on every part file in folder X,” stop and read the Task Scheduler task configuration dialog carefully.

The Update Custom Properties and Save As task types — and the generic Run Macro task when configured in Process Documents style — both include a folder selection field in their setup. You paste the source folder directly into the UI. Task Scheduler iterates the folder, opens each file, and calls your macro once per file with that file already active.

Inside the macro, you get the current file path from the active document:

Sub main()
    Dim swApp As SldWorks.SldWorks
    Set swApp = Application.SldWorks
    
    Dim swDoc As SldWorks.ModelDoc2
    Set swDoc = swApp.ActiveDoc
    
    If swDoc Is Nothing Then Exit Sub
    
    ' The full path of the currently active document
    Dim docPath As String
    docPath = swDoc.GetPathName()
    
    ' Process it
    Dim custMgr As SldWorks.CustomPropertyManager
    Set custMgr = swDoc.Extension.CustomPropertyManager("")
    
    Dim propVal As String, resolvedVal As String, wasResolved As Long
    custMgr.Get6 "Description", False, propVal, resolvedVal, wasResolved
    
    ' Write a derived property based on the filename
    Dim partNum As String
    partNum = Left(Mid(docPath, InStrRev(docPath, "\") + 1), _
                  InStr(Mid(docPath, InStrRev(docPath, "\") + 1), ".") - 1)
    
    custMgr.Add3 "PartNumber", swCustomInfoType_e.swCustomInfoText, _
                 partNum, swCustomPropertyAddOption_e.swCustomPropertyOnlyIfNew
    
    swDoc.Save3 swSaveAsOptions_e.swSaveAsOptions_Silent, 0, 0
End Sub

No config file needed. The folder came from the Task Scheduler task definition. The macro gets the file path from ActiveDoc. This is the clean version of what John Sitek’s forum answer described as a “standalone approach” — except here it wires directly into Task Scheduler’s Process Documents mode instead of running as an independent loop.

Handling Multiple Configurations

One common scenario: a part has multiple configurations (Default, Lightweight, As Machined), and you need to run per-configuration processing. The CustomPropertyManager takes a configuration name:

' Process all configurations in the active document
Sub ProcessAllConfigs(swDoc As SldWorks.ModelDoc2)
    Dim configNames As Variant
    configNames = swDoc.GetConfigurationNames()
    
    Dim i As Integer
    For i = 0 To UBound(configNames)
        Dim configName As String
        configName = CStr(configNames(i))
        
        Dim custMgr As SldWorks.CustomPropertyManager
        Set custMgr = swDoc.Extension.CustomPropertyManager(configName)
        
        ' Read or write config-specific properties
        Dim propVal As String, resolvedVal As String, wasResolved As Long
        custMgr.Get6 "Description", False, propVal, resolvedVal, wasResolved
        
        ' ... process propVal
    Next i
End Sub

For the general-purpose configuration of custom property management, the SolidWorks workflow automation guide covers where macros fit relative to Task Scheduler, PDM Dispatch, and C# add-ins — useful context before deciding how complex the parameter-passing scheme needs to be.

Running Task Scheduler from the Command Line

One pattern that avoids the parameter-passing problem entirely: run the task from a PowerShell script that writes the config file, then invokes the Task Scheduler executable directly. This is useful when Task Scheduler runs as part of a CI pipeline or is triggered by an external system.

# Write config file with tonight's batch folder
$batchFolder = "C:\Projects\Batch_$(Get-Date -Format 'yyyyMMdd')"
Set-Content -Path "C:\CadTools\task_config.txt" -Value "FolderPath = $batchFolder"

# Run the Task Scheduler task synchronously
$tsExe = "C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS Task Scheduler\SWTaskSchedulerSrv.exe"
$taskFile = "C:\CadTools\NightlyBatchTask.swts"  # saved task definition

& $tsExe /runTask $taskFile /wait

The /wait flag blocks until the task finishes. Exit code 0 means success; non-zero means something failed during processing.

What the Hallucinated Code Was Actually Trying to Do

For context: the forum user’s original code used $$$FOLDER_PATH$$$ as a template variable that they expected the Task Scheduler to substitute before passing the macro to VBA. It also called CustomPropertyData.GetFolderPath() as if a built-in class existed for this purpose.

Neither mechanism exists. The substitution idea has a parallel in some templating systems — Liquid, Mustache, even Word mail merge — but the SolidWorks macro runner is a plain VBA interpreter. It does not preprocess source code. What you write is what executes.

The CustomPropertyData class may have come from conflating ICustomPropertyManager (the real API) with a data-access object naming pattern from other frameworks. The SolidWorks API does not have helper classes like that; every operation goes through the COM interface hierarchy starting from ISldWorks.

If you are auditing AI-generated SolidWorks macro code, check for:

  • Any identifier ending in Data, Helper, Provider, or Service — likely hallucinated
  • Token syntax with $$$, %, {{, or [[ in VBA source code — does not exist
  • swApp.GetExtension() or swApp.GetService() — these methods do not exist on ISldWorks
  • Application.GetCurrentDocument() — not a SolidWorks API call

The AI coding failures post covers the broader pattern of why language models produce plausible-looking but non-functional SolidWorks code.

Summary

SOLIDWORKS Task Scheduler macros cannot receive parameters through the macro itself. The two patterns that work:

Use casePattern
Process Documents mode — operate on each file in a folderNo parameter passing needed — folder is in the Task Scheduler task UI
Run Macro mode — macro manages its own file I/OConfig file (text key=value) or Windows Registry

Both patterns require one hardcoded path in the macro: either the config file path or the registry key root. That hardcoded path is the only dependency that needs to change when you clone a task for a different folder. Everything else — the batch logic, the per-file processing, the error handling — stays identical.

CadShift’s flat-pattern export works on top of this same pattern. If you need batch DXF export that behaves consistently across folders and doesn’t break when the source folder changes, the batch DXF export tools comparison covers what the standalone macro approach misses versus what a purpose-built add-in handles.