Driving AutoCAD from PowerShell via COM — Scripted Drafting
For drafting work that consists of repeated dimensions from a table, clicking through one line at a time is a waste of time. AutoCAD has long exposed COM for external control, and PowerShell can talk to COM directly without installing anything extra.
I had a job designing several steel frames with slightly different sizes and a large set of dimension tables. Drawing each line manually in the program would easily take all day. Then if the client asked to change a single dimension, I would have to redo the whole set. So I looked into whether AutoCAD could be controlled from outside the program. It turns out it has supported this for years, through COM, just like Excel, and PowerShell can talk to COM directly without installing anything extra.
What I like about this approach is that it turns drafting into source code — change the numbers in the script and run it again to get a whole new set of drawings. And when I ask an AI assistant to help draft it, it can write the PowerShell more precisely than I can explain to someone which points to draw lines between. I have run everything in this article and produced actual files.
Connect to the running instance
It starts with one line. The number at the end of the ProgID is the program version (.25 = AutoCAD 2025; adjust it to match your installation):
$acad = New-Object -ComObject AutoCAD.Application.25
$doc = $acad.ActiveDocument
$ms = $doc.ModelSpace
New-Object will connect to the running instance if one already exists. It starts a new instance only if none is running. The benefit is seeing the results live as I issue commands. Lines appear on screen as soon as each line of the script runs, making mistakes very easy to track down.
The first trap to clear before drawing: the "point" format
All AutoCAD methods accept coordinates as an array of three doubles, not three separate numbers. PowerShell has a habit of unrolling arrays when returning them from functions. A straightforward implementation produces strange type mismatch errors, even though the result looks like those same three numbers. The fix is to add a leading comma when returning to wrap the array so it does not get unrolled:
function Pt($x, $y, $z = 0) {
$p = [System.Array]::CreateInstance([double], 3)
$p[0] = $x; $p[1] = $y; $p[2] = $z
return ,$p # จุลภาคหน้า $p สำคัญมาก - กันไม่ให้อาร์เรย์ถูกคลี่
}
This short function is used throughout the script. I spent quite a while on this single line before realizing that the problem was PowerShell's behavior, not AutoCAD.
Drawing the basics
This covers everything needed for typical work. The units are drawing units; I use millimeters:
# เส้นตรง: จุดเริ่ม -> จุดจบ
$ms.AddLine((Pt 0 0), (Pt 4000 0))
# วงกลม: จุดศูนย์กลาง, รัศมี
$ms.AddCircle((Pt 0 0), 500)
# ข้อความ: เนื้อความ, จุดวาง, ความสูงตัวอักษร
$ms.AddText("COL-01", (Pt 100 200), 250)
# เส้นต่อเนื่องหลายจุด (polyline) - อาร์เรย์แบนของ x,y,z เรียงต่อกัน
$pts = [System.Array]::CreateInstance([double], 9)
$pts[0]=0; $pts[1]=0; $pts[2]=0
$pts[3]=4000; $pts[4]=0; $pts[5]=0
$pts[6]=4000; $pts[7]=3000; $pts[8]=0
$ms.AddPolyline($pts)
# ทรงตันสามมิติ: จุดศูนย์กลางกล่อง, กว้าง, ยาว, สูง
$box = $ms.AddBox((Pt 2000 1500 1250), 100, 100, 2500)
Layers and colors
A drawing without layers is a drawing that cannot be edited properly. All of this can be done from the script, and it should be done at the start because moving things later takes more time:
$layers = @{ 'COLUMN' = 5; 'BEAM' = 3; 'PURLIN' = 2; 'DIM' = 8 }
foreach ($name in $layers.Keys) {
$l = $doc.Layers.Add($name)
$l.Color = $layers[$name] # เลขสี ACI: 1=แดง 2=เหลือง 3=เขียว 5=ฟ้า 8=เทา
}
# กำหนดเลเยอร์ให้วัตถุตอนสร้างเลย
$ms.AddBox((Pt 0 0 1250), 100, 100, 2500).Layer = 'COLUMN'
$ms.AddLine((Pt 0 2500), (Pt 6000 2500)).Layer = 'BEAM'
# หรือตั้งเลเยอร์ปัจจุบันไว้ก่อน แล้ววาดตามปกติ
$doc.ActiveLayer = $doc.Layers.Item('PURLIN')
Dimensions
AddDimRotated takes four values: the first point, the second point, the position of the dimension text, and the dimension line angle in radians, not degrees. This is very easy to get wrong:
# ระยะแนวนอน: มุม 0
$ms.AddDimRotated((Pt 0 0), (Pt 4000 0), (Pt 2000 -600), 0).Layer = 'DIM'
# ระยะแนวตั้ง: มุม pi/2
$ms.AddDimRotated((Pt 0 0), (Pt 0 3000), (Pt -600 1500), [Math]::PI / 2).Layer = 'DIM'
# ปรับขนาดตัวเลขให้อ่านออกในงานสเกลใหญ่
$doc.SetVariable("DIMTXT", 200)
$doc.SetVariable("DIMASZ", 150)
Every part of this API that accepts an angle takes radians, not degrees. Passing 90 does not produce an error, but the dimension line ends up at some unexpected angle, leaving me wondering why the drawing is a mess. It is safer to write a helper such as function Deg($d){ $d * [Math]::PI / 180 } and always use it.
Saving the file
$doc.SaveAs("D:\work\frame-01.dwg")
That really is all it takes. The resulting file opens normally in the program and can be sent to someone else for further editing. There is nothing malformed about it.
The most painful trap: never call Quit
COM connects to the instance that is already running, so $acad.Quit() does not just close what the script created. It closes the entire program along with any drawings the user has open. I absentmindedly added it at the end of the script out of habit from working with Excel, and the drawing I had open in another window disappeared right before my eyes. At the end of the script, just release the COM reference. Do not tell the program to close.
When the work is done, do only this:
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($ms) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($doc) | Out-Null
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($acad) | Out-Null
[GC]::Collect()
Three more traps
1. Reading objects back requires for, not foreach — To inspect what is in the drawing, foreach ($e in $ms) does not work. The COM collection does not provide the kind of enumerator PowerShell expects. I have to iterate directly by index, and if I have just called Open, I also need to wait for the file to finish loading; otherwise, I get an empty collection:
$doc = $acad.Documents.Open("D:\work\frame-01.dwg")
Start-Sleep -Seconds 5 # รอให้โหลดจบจริงก่อน
$ms = $doc.ModelSpace
for ($i = 0; $i -lt $ms.Count; $i++) {
$e = $ms.Item($i)
"{0,-20} layer={1}" -f $e.ObjectName, $e.Layer
}
2. Connect members as they fit in real life; do not run every member through the center — This lesson came directly from the steel frame job. At first, I drew every member along the centerlines because that made the calculations easiest. The drawing looked good on screen, but when I showed it to the fabricator, it could not be assembled: real steel members sit on one another rather than pass through one another.
Easy to calculate, impossible to assemble
Every member passes through the same point. The steel ends overlap in a lump, duplicating volume and inflating the material takeoff. The fabricator has to ask what lengths to actually cut.
Ready to cut the steel
The columns are full height, and the beams sit on top of them. Short beams that butt between long beams are shortened by the thickness of the members they meet. Purlins sit on another layer above. The resulting dimensions are the actual cutting lengths.
Once the stacking order is correct, the lengths of members that fit between other members must account for the thickness of the members they meet. The script just subtracts the values in order. It is no harder, but the results are completely different.
3. Do not rely on SendCommand for important work — SendCommand feeds in command text as though it were typed into the command line. I tried using it to export PDFs, and sometimes it did nothing; other times it only got halfway through. Those commands involve a series of prompts and responses, and the prompts differ between versions. I ended up dropping it. All the main work — creating objects, reading them back, layers, dimensions, and saving — can be done through direct methods without relying on SendCommand even once.
Conclusion
Personally, I see the value of this approach as more than just "drawing faster than clicking." It turns a drawing into a text file whose history I can track, whose versions I can compare, and which I can use to reproduce the same drawing every time. For work where parameters change often, such as steel frames or shelving layouts, it is well worth it. For design work that requires visual judgment at each point, I still draw manually. There is no need to force it.
To build on this foundation, start with just the Pt function and a complete set of layers. Add the rest gradually as the job requires. If I have left anything out or made a typo, I will come back and fix it later.
Hit something like this? Stuck somewhere?
Come trade notes on the LinuxThai board



Comments 0
No comments yet — be the first