ASPPY is a Python-based runtime that executes Classic ASP (VBScript) pages. It provides compatibility with most VBScript built-in functions, the Classic ASP object model (Request, Response, Session, Application, Server), and various COM components commonly used in legacy ASP applications.
Platform Support
ASPPY works on:
- Windows
- Linux
- macOS
Dependencies
The following Python libraries are required:
| Library | Purpose |
|---|---|
| Python 3.8+ | Runtime environment |
| fpdf2 | PDF generation (pip install fpdf2) |
| bcrypt | Password hashing (pip install bcrypt) |
| Pillow | Image processing (pip install pillow) |
| pyodbc | Access/Excel/ODBC database providers (pip install pyodbc) |
| certifi (optional) | TLS CA bundle for MSXML HTTP (pip install certifi) |
| zipfile (built-in) | ZIP file handling |
VBScript Built-in Functions
Compatibility notes for built-ins:
- ASPPY targets practical Classic ASP/VBScript compatibility, but does not guarantee byte-for-byte parity with IIS VBScript for every edge case.
- The function lists below describe the exposed runtime surface; behavior can still differ in locale, coercion, error wording, and edge conditions.
- Functions implemented through compatibility shims/stubs (for example formatting/script-engine helpers) are intended for app portability, not strict engine emulation.
- SQL/dialect translation is intentionally out of scope; database commands are executed as provided by the script and underlying provider/driver.
- If exact behavior matters for a critical path, validate with your app-specific regression tests.
String Functions
| Function | Description |
|---|---|
Len(string) | Returns the length of a string |
LenB(expression) | Returns the length of a string in bytes |
UCase(string) | Converts a string to uppercase |
LCase(string) | Converts a string to lowercase |
Trim(string) | Removes leading and trailing spaces |
LTrim(string) | Removes leading spaces |
RTrim(string) | Removes trailing spaces |
StrReverse(string) | Reverses a string |
StrComp(string1, string2[, compare]) | Compares two strings |
Left(string, length) | Returns leftmost characters |
Right(string, length) | Returns rightmost characters |
Mid(string, start[, length]) | Returns characters from a string |
LeftB(string, length) | Returns leftmost bytes |
RightB(string, length) | Returns rightmost bytes |
MidB(expr, start[, length]) | Returns bytes from a string |
InStr([start, ]string1, string2[, compare]) | Finds one string within another |
InStrB([start, ]string1, string2) | Finds one string within another (byte) |
Replace(expression, find, replace[, start[, count[, compare]]]) | Replaces text in a string |
Split(expression[, delimiter[, count[, compare]]]) | Splits a string into an array |
Join(list[, delimiter]) | Joins an array into a string |
Filter(inputstrings, value[, include[, compare]]) | Returns a filtered array |
Space(number) | Returns a string of spaces |
String(number, character) | Returns a repeating character string |
Asc(string) | Returns the ANSI code of the first character |
AscW(string) | Returns the Unicode code of the first character |
AscB(string) | Returns the first byte of a string |
Chr(charcode) | Returns the character associated with an ANSI code |
ChrW(charcode) | Returns the character associated with a Unicode code |
ChrB(charcode) | Returns a single-byte character |
Hex(number) | Returns the hexadecimal value |
Oct(number) | Returns the octal value |
Array Functions
| Function | Description |
|---|---|
Array(arglist) | Creates an array |
IsArray(varname) | Returns True if variable is an array |
LBound(arrayname[, dimension]) | Returns the lowest subscript |
UBound(arrayname[, dimension]) | Returns the highest subscript |
Type Conversion Functions
| Function | Description |
|---|---|
CBool(expression) | Converts to Boolean |
CByte(expression) | Converts to Byte |
CCur(expression) | Converts to Currency |
CDbl(expression) | Converts to Double |
CInt(expression) | Converts to Integer |
CLng(expression) | Converts to Long |
CSng(expression) | Converts to Single |
CStr(expression) | Converts to String |
CDate(date) | Converts to Date |
Date/Time Functions
| Function | Description |
|---|---|
Now() | Returns current date and time |
Date() | Returns current system date |
Time() | Returns current system time |
Timer() | Returns seconds since midnight |
Year(date) | Returns the year |
Month(date) | Returns the month (1-12) |
Day(date) | Returns the day (1-31) |
Hour(time) | Returns the hour (0-23) |
Minute(time) | Returns the minute (0-59) |
Second(time) | Returns the second (0-59) |
DateSerial(year, month, day) | Returns a date |
TimeSerial(hour, minute, second) | Returns a time |
DateAdd(interval, number, date) | Adds a time interval |
DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]]) | Returns the difference |
DatePart(interval, date[, firstdayofweek[, firstweekofyear]]) | Returns a part of a date |
Weekday(date[, firstdayofweek]) | Returns the weekday (1-7) |
WeekdayName(weekday[, abbreviate[, firstdayofweek]]) | Returns the weekday name |
MonthName(month[, abbreviate]) | Returns the month name |
DateValue(string) | Returns a date from a string |
TimeValue(string) | Returns a time from a string |
CDate(string) | Converts a string to a date |
IsDate(expression) | Returns True if expression is a date |
FormatDateTime(date[, namedformat]) | Formats a date/time |
Date Constants
| Constant | Value | Description |
|---|---|---|
vbSunday | 1 | Sunday |
vbMonday | 2 | Monday |
vbTuesday | 3 | Tuesday |
vbWednesday | 4 | Wednesday |
vbThursday | 5 | Thursday |
vbFriday | 6 | Friday |
vbSaturday | 7 | Saturday |
vbUseSystemDayOfWeek | 0 | Use system day of week |
vbFirstJan1 | 1 | First week with Jan 1 |
vbFirstFourDays | 2 | First week with 4 days |
vbFirstFullWeek | 3 | First full week |
vbGeneralDate | 0 | General date format |
vbLongDate | 1 | Long date format |
vbShortDate | 2 | Short date format |
vbLongTime | 3 | Long time format |
vbShortTime | 4 | Short time format |
vbBinaryCompare | 0 | Binary comparison |
vbTextCompare | 1 | Text comparison |
Math Functions
| Function | Description |
|---|---|
Abs(number) | Returns absolute value |
Atn(number) | Returns arctangent |
Cos(number) | Returns cosine |
Exp(number) | Returns e raised to a power |
Fix(number) | Returns integer portion |
Int(number) | Returns integer portion (floor) |
Log(number) | Returns natural logarithm |
Rnd([number]) | Returns random number |
Round(expression[, numdecimalplaces]) | Rounds a number |
Sqr(number) | Returns square root |
Sgn(number) | Returns sign of a number |
Format Functions
Note: formatting functions are compatibility-focused and may differ from IIS under some locale/regional settings.
| Function | Description |
|---|---|
FormatNumber(expression[, numdigitsafterdecimal[, includeleadingdigit[, useparensfornegativenumbers[, groupdigits]]]]) | Formats a number |
FormatCurrency(expression[, numdigitsafterdecimal[, includeleadingdigit[, useparensfornegativenumbers[, groupdigits]]]]) | Formats as currency |
FormatPercent(expression[, numdigitsafterdecimal[, includeleadingdigit[, useparensfornegativenumbers[, groupdigits]]]]) | Formats as percentage |
Information Functions
Note: type/coercion semantics generally follow VBScript, but may differ in edge cases involving Empty/Null/Nothing and host objects.
| Function | Description |
|---|---|
IsArray(varname) | Returns True if variable is an array |
IsDate(expression) | Returns True if expression is a date |
IsEmpty(expression) | Returns True if variable is Empty |
IsNull(expression) | Returns True if expression is Null |
IsNumeric(expression) | Returns True if expression is numeric |
IsObject(expression) | Returns True if expression is an object |
TypeName(varname) | Returns the type name |
VarType(varname) | Returns the variant type |
VarType Constants
| Constant | Value | Description |
|---|---|---|
vbEmpty | 0 | Empty (uninitialized) |
vbNull | 1 | Null (no valid data) |
vbInteger | 2 | Integer |
vbLong | 3 | Long integer |
vbSingle | 4 | Single-precision |
vbDouble | 8 | Double-precision |
vbCurrency | 6 | Currency |
vbDate | 7 | Date |
vbString | 8 | String |
vbObject | 9 | Object |
vbBoolean | 11 | Boolean |
vbArray | 8192 | Array |
Color Functions
| Function | Description |
|---|---|
RGB(red, green, blue) | Returns an RGB color value |
Classic ASP Objects
Server-Side Includes
<!--#include file="..." --> and <!--#include virtual="..." -->
are resolved before the page runs. The two forms differ only in what the path is relative to:
| Form | Resolved relative to |
|---|---|
file="x.inc" | The folder of the file containing the directive. Inside a nested include this is the include's own folder, not the requesting page's, so an .inc can pull in its siblings without knowing who included it. |
virtual="/x.inc" | The application root, at every nesting depth. A bare virtual="x.inc" (no leading slash) is also accepted and is treated as application-root relative. |
Matching IIS, the scanner accepts backslashes as separators, ./ and ../ segments, single or double quotes, and loose whitespace around #include and =. Paths that climb above the application root are refused.
A file included twice is expanded twice. The directive is a textual
splice, not an idempotent import, so including a snippet at two points in a page emits it
at both — there is no #pragma once behaviour and no de-duplication.
That also applies when the same file is reached by two different spellings
(file= in one place, virtual= in another), or directly in one
place and transitively through another include. Because definitions are hoisted before
execution, a duplicated include that declares a Sub or Function
does not raise a redefinition error; only its top-level statements and markup repeat.
A genuine cycle — a.inc including b.inc including
a.inc — is an error, not a silently truncated expansion, and fails the
request as IIS does. Detection walks the current include chain, which is what keeps
legitimate repeats working; nesting deeper than 32 levels is also refused.
Text that merely looks like a directive is left alone: #include inside a
VBScript string literal or inside an ASP comment is inert.
Server Object
Methods
| Method | Description |
|---|---|
CreateObject(progid) | Creates an instance of a COM object |
HTMLEncode(string) | Encodes HTML characters |
URLEncode(string) | URL-encodes a string |
MapPath(path) | Maps a virtual path to a physical path. Never appends a trailing separator, even when path has one — MapPath("data/x/") and MapPath("data/x") both return ...\data\x, matching IIS, which cannot know whether the target is a file or a folder because it never touches the filesystem. So Server.MapPath("data/x/") & "f.txt" silently yields ...\data\xf.txt; put the filename inside the call instead: Server.MapPath("data/x/f.txt"). An empty path raises (see Runtime Error Parity). |
Execute(path) | Executes an ASP file |
Transfer(path) | Transfers execution to another ASP file |
GetLastError() | Returns the last error object |
Properties
| Property | Description |
|---|---|
ScriptTimeout | Gets/sets script timeout in seconds |
Extension Methods (ASPPY-specific)
| Method | Description |
|---|---|
ASPPYListAspPages() | Lists all .asp pages under docroot |
ASPPYRun(virtual_path) | Runs another ASP page and captures output |
Supported Server.CreateObject ProgIDs
Scripting.DictionaryScripting.FileSystemObjectADODB.Connection,ADODB.Recordset,ADODB.Command,ADODB.StreamVBScript.RegExp/RegExpMSXML2.ServerXMLHTTP,MSXML2.XMLHTTP,MSXML2.DOMDocument(incl..3.0/.6.0aliases)Microsoft.XMLDOM,MSXML.DOMDocumentCDO.Message,CDOSYS.MessageASPPY.POP3,ASPPY.POP3,ASPPY.IMAP,ASPPY.IMAP
Request Object
Properties
| Property | Description |
|---|---|
QueryString | NameValueCollection of query string parameters |
Form | NameValueCollection of form data parsed from POST only |
Cookies | NameValueCollection of cookies |
ServerVariables | NameValueCollection of server variables |
TotalBytes | Total bytes in request body |
Method / HttpMethod | HTTP method (GET, POST, PUT, DELETE, etc.) |
Files | Collection of uploaded files |
Request (default) | Access to QueryString, Form, Cookies |
Methods
| Method | Description |
|---|---|
BinaryRead(count) | Reads raw bytes from request body |
HTTP methods and body parsing
- ASPPY passes through HTTP methods to ASP pages without a verb allow-list.
Request.ServerVariables("REQUEST_METHOD")returns the incoming verb string (for example:GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS).Request.Formis parsed forPOSTwith supported form encodings (application/x-www-form-urlencoded,multipart/form-data).- For non-
POSTbodies (PUT,DELETE,PATCH, etc.), read raw bytes viaRequest.BinaryRead(...);Request.Formremains empty. Request.TotalBytesreflects the request body size for any method.
Request.Files Collection
Access uploaded files from multipart form submissions.
' Iterate over all uploaded files
For Each file In Request.Files
Response.Write(file.FileName)
Response.Write(file.Size)
Response.Write(file.ContentType)
' Save to disk
file.SaveAs Server.MapPath("/uploads/" & file.FileName)
Next
' Access specific file
Set f = Request.Files("myfile")
If Not f Is Nothing Then
Response.Write f.Name
Response.Write f.FileName
Response.Write f.Size
Response.Write f.ContentType
End If
' Check if file exists
If Request.Files.Exists("myfile") Then
' File was uploaded
End If
' Get count
Response.Write Request.Files.Count
UploadedFile Properties
| Property | Description |
|---|---|
Name | Form field name |
FileName | Original filename |
ContentType | MIME content type |
Size | File size in bytes |
UploadedFile Methods
| Method | Description |
|---|---|
SaveAs(path) | Saves the file to disk |
Files Collection Methods
| Method | Description |
|---|---|
Count | Number of uploaded files |
Exists(name) | Checks if file with given name exists |
Item(name) | Gets uploaded file by name |
Keys() | Returns array of field names |
Items() | Returns array of UploadedFile objects |
Response Object
Properties
| Property | Description |
|---|---|
Buffer | Enables/disables response buffering |
Cookies | Collection of cookies to send |
LCID | Locale identifier |
ContentType | MIME type of the response (default text/html) |
Charset | Character set of the response body. Defaults to utf-8 - see Character Encoding |
CodePage | Codepage for this response. Defaults to 65001 (UTF-8), not the host ANSI codepage as under IIS |
Methods
| Method | Description |
|---|---|
Write(string) | Writes output to the response |
BinaryWrite(data) | Writes binary data |
Clear() | Clears the buffered output |
Flush() | Flushes buffered output |
End() | Stops script execution |
AddHeader(name, value) | Adds a custom header |
AppendToLog(message) | Appends to server log |
Redirect(url) | Redirects to another URL |
File(path[, inline]) | Serves a file (inline=True for display, False for download) |
Session Object
Properties
| Property | Description |
|---|---|
SessionID | Unique session identifier |
Timeout | Session timeout in minutes |
Contents | Collection of session variables |
StaticObjects | Collection of session-scoped objects |
LCID | Locale identifier for the session |
CodePage | Codepage for the session. Defaults to 65001 (UTF-8), not the host ANSI codepage as under IIS - see Character Encoding |
Methods
| Method | Description |
|---|---|
Abandon() | Ends the session |
Application Object
Properties
| Property | Description |
|---|---|
Contents | Collection of application variables |
StaticObjects | Collection of static objects |
Methods
| Method | Description |
|---|---|
Lock() | Locks application variables |
Unlock() | Unlocks application variables |
ASPPY Extended Objects
ASPPY Object
The ASPPY object provides additional functionality beyond classic ASP.
It is available as global ASPPY in script scope.
How members are named
Member access is always case-insensitive, so casing never matters. The shape of a name follows one rule:
Objects that exist on IIS use PascalCase. Wrappers around a third-party library keep that library's own names, so its documentation transfers unchanged.
| Surface | Convention | Example |
|---|---|---|
Response, Request, Server, Session, Application, ADODB, FSO, CDO, Scripting.Dictionary, VBScript.RegExp | PascalCase, exactly as IIS | Response.BinaryWrite |
MSXML2.* | camelCase — the real MSXML COM API is camelCase | doc.selectSingleNode |
ASPPY.Image | lowercase — mirrors Pillow | img.thumbnail |
ASPPY.Pdf | snake_case — mirrors fpdf2 (PascalCase aliases also provided, see PDF) | pdf.set_margins / pdf.SetMargins |
ASPPY.JSON, .Zip, .Crypto, ExecutePython | PascalCase — no upstream API to mirror | ASPPY.JSON.Encode |
If a member name is wrong, error 438 names the closest match rather than leaving you to guess: Unknown member: SETMARGINZ on PdfDoc (did you mean 'SetMargins'?).
Members beginning with _ are host-side internals. VBScript identifiers cannot start with an underscore, so they are unreachable from script and, like on IIS, raise 438.
JSON
ASPPY.JSON.Encode(value[, pretty]) ' Returns JSON string
ASPPY.JSON.Decode(json_string) ' Returns VBScript value
Note: member access is case-insensitive in VBScript; runtime members are exposed as json, zip, image, crypto, pdf.
ZIP
ASPPY.Zip.Zip(path[, out_path]) ' Creates a ZIP file
ASPPY.Zip.Unzip(zip_path, dest_folder[, overwrite]) ' Extracts a ZIP file
Image (Pillow)
ASPPY.Image.open(path) ' Opens an image file
ASPPY.Image.new(mode, size, color) ' Creates a new image
ASPPY.Image.merge(mode, bands) ' Merges image bands
ASPPY.Image.blend(img1, img2, alpha) ' Blends two images
ASPPY.Image.composite(img1, img2, mask) ' Creates composite
ASPPY.ImageDraw.Draw(img) ' Creates a draw object
ASPPY.ImageFilter.BLUR ' Blur filter constant
ASPPY.ImageFilter.CONTOUR ' Contour filter
ASPPY.ImageFilter.EDGE_ENHANCE ' Edge enhancement
ASPPY.ImageFilter.SHARPEN ' Sharpen filter
ASPPY.ImageFilter.GaussianBlur(radius) ' Gaussian blur
ASPPY.ImageEnhance.Brightness(img) ' Brightness enhancer
ASPPY.ImageEnhance.Contrast(img) ' Contrast enhancer
ImageInstance Properties/Methods
| Property/Method | Description |
|---|---|
size | Image dimensions (width, height) |
width | Image width |
height | Image height |
mode | Image color mode |
format | Image format |
save(path) | Saves the image |
resize(size) | Resizes the image |
thumbnail(size) | Creates thumbnail |
crop(box) | Crops the image |
rotate(angle) | Rotates the image |
convert(mode) | Converts color mode |
split() | Splits into bands |
getpixel(xy) | Gets pixel value |
putpixel(xy, value) | Sets pixel value |
filter(filter_obj) | Applies filter |
paste(other_img, box[, mask]) | Pastes another image |
PDF (FPDF)
Set pdf = ASPPY.Pdf.New([orientation[, unit[, format]]])
PdfDoc Methods
| Method | Description |
|---|---|
add_page([orientation]) | Adds a new page |
set_margins(left, top[, right]) | Sets page margins |
set_auto_page_break(auto[, margin]) | Sets auto page break |
set_font(family[, style[, size]]) | Sets the font |
set_text_color(r[, g[, b]]) | Sets text color |
set_draw_color(r[, g[, b]]) | Sets drawing color |
set_fill_color(r[, g[, b]]) | Sets fill color |
set_line_width(width) | Sets line width |
fill_page(r[, g[, b]]) | Fills the page with color |
text(x, y, text) | Writes text at position |
cell(w[, h[, text[, border[, ln[, align[, fill[, link]]]]]]]) | Writes a cell |
multi_cell(w, h, text[, border[, align[, fill]]]) | Writes multi-cell |
set_xy(x, y) | Sets current position |
ln([h]) | Moves to next line |
image(path[, x[, y[, w[, h]]]]) | Adds an image |
output(path) | Saves PDF to file |
Crypto (bcrypt)
ASPPY.Crypto.Hash(password[, rounds]) ' Hashes a password (rounds 4-31, default 12)
ASPPY.Crypto.Verify(password, hashed) ' Verifies password against hash
ExecutePython
ASPPY.ExecutePython(code [, args] [, timeout]) ' Executes inline Python source, returns ASPPY_RETURN value as string
ASPPY.ExecutePythonFile(path [, args] [, timeout]) ' Executes a .py file (relative to docroot), returns ASPPY_RETURN value
Enable with environment variable ASP_PY_ALLOW_PYTHON=1. Each call spawns an isolated Python subprocess. The Python snippet uses ASPPY_RETURN(value) to return a string to VBScript.
Both optional arguments work the same way on either method. args takes any JSON-encodable value (string, number, boolean, Array, Scripting.Dictionary, or a nesting of those) and the Python side reads it as the built-in ASPPY_ARGS, already decoded — it is None when the argument is omitted. timeout is a per-call limit in seconds that overrides ASP_PY_PYTHON_TIMEOUT. See the ASPPY.ExecutePython page for full documentation, samples, and environment variables.
COM Objects
Scripting.Dictionary
Set dict = Server.CreateObject("Scripting.Dictionary")
| Property/Method | Description |
|---|---|
Count | Number of items |
CompareMode | Comparison mode (0=Binary, 1=Text, 2=Database) |
Item(key) | Gets/sets item value |
Keys | Returns array of keys |
Items | Returns array of items |
Add(key, item) | Adds a key/item pair |
Exists(key) | Returns True if key exists |
Remove(key) | Removes a key |
RemoveAll() | Removes all items |
Scripting.FileSystemObject
Set fso = Server.CreateObject("Scripting.FileSystemObject")
| Property/Method | Description |
|---|---|
BuildPath(path, name) | Builds a path |
CreateTextFile(filename[, overwrite[, unicode]]) | Creates text file |
OpenTextFile(filename[, iomode[, create[, format]]]) | Opens text file |
GetFile(filepath) | Gets File object |
GetFolder(folderpath) | Gets Folder object |
GetDrive(drivespec) | Gets Drive object |
DriveExists(drivespec) | Checks if drive exists |
FileExists(filepath) | Checks if file exists |
FolderExists(folderpath) | Checks if folder exists |
File Object
| Property/Method | Description |
|---|---|
Path | Full path |
Name | File name |
Size | File size |
Type | File type |
DateCreated | Creation date |
DateLastAccessed | Last access date |
DateLastModified | Last modified date |
Drive | The Drive OBJECT. Its default property is Path, so "" & f.Drive gives C: (no trailing separator); f.Drive.RootFolder.Path gives C:\ |
ParentFolder | Parent folder |
ShortName | 8.3 NAME only, no path (contrast ShortPath) |
ShortPath | 8.3 path |
Attributes | File attributes |
Copy(destination[, overwrite]) | Copies file |
Move(destination) | Moves file |
Delete([force]) | Deletes file |
OpenAsTextStream([iomode[, format]]) | Opens as text stream |
Folder Object
| Property/Method | Description |
|---|---|
Path | Full path |
Name | Folder name |
Size | Total size of folder |
DateCreated | Creation date |
DateLastAccessed | Last access date |
DateLastModified | Last modified date |
Drive | The Drive OBJECT. Its default property is Path, so "" & f.Drive gives C: (no trailing separator); f.Drive.RootFolder.Path gives C:\ |
IsRootFolder | True if root |
Files | Files collection |
SubFolders | Subfolders collection |
Attributes | Folder attributes |
Copy(destination[, overwrite]) | Copies folder |
Move(destination) | Moves folder |
Delete([force]) | Deletes folder |
TextStream Object
| Property/Method | Description |
|---|---|
AtEndOfStream | True at end of file |
Read(n) | Reads n characters |
ReadLine() | Reads a line |
ReadAll() | Reads entire file |
Write(string) | Writes string |
WriteLine([string]) | Writes line |
WriteBlankLines(n) | Writes blank lines |
Close() | Closes the stream |
Skip(n) | Skips n characters |
SkipLine() | Skips a line |
VBScript.RegExp
Set regex = Server.CreateObject("VBScript.RegExp")
| Property/Method | Description |
|---|---|
Pattern | Regular expression pattern |
IgnoreCase | Case-insensitive matching |
Global | Match all occurrences |
MultiLine | Multi-line matching |
Test(string) | Tests for a match |
Replace(string, replace_with) | Replaces matches |
Execute(string) | Returns match collection |
ADODB.Connection
Set conn = Server.CreateObject("ADODB.Connection")
| Property/Method | Description |
|---|---|
ConnectionString | Connection string |
State | Connection state |
CommandTimeout | Command timeout |
CursorLocation | Cursor location |
Version | ADO library version, "10.0" (what IIS on Windows 10 reports) |
IsolationLevel | adXactReadCommitted (4096) |
Attributes | ConnectOptionEnum bitmask, 0 |
Properties | Provider property collection — present so scripts run, but always empty (see the note below) |
Open([connection_string]) | Opens connection |
Close() | Closes connection |
Execute(sql[, records_affected[, options]]) | Executes SQL |
OpenSchema(queryType[, criteria]) | Schema rowsets — see below |
BeginTrans() | Begins transaction |
CommitTrans() | Commits transaction |
RollbackTrans() | Rolls back transaction |
Connection string notes:
- Execution support is currently SQLite (
sqlite3), Access (pyodbc+ Access ODBC driver), Excel (pyodbc+ Excel ODBC driver), and generic ODBC (pyodbc) in this runtime. - ASPPY now parses and classifies common legacy ADO connection string families (Access, SQL Server, ODBC, MySQL, Oracle, PostgreSQL) so unsupported providers fail with explicit migration guidance instead of generic errors.
- For SQLite, use
Provider=SQLite;Data Source=<path>(orProvider=SQLite;Database=<path>), or a bare file path. - For Access, use legacy Access-style strings (
Provider=Microsoft.Jet.OLEDB.4.0/Provider=Microsoft.ACE.OLEDB.12.0) withData Source=<path to .mdb/.accdb>. - For Excel, use legacy Excel-style strings (for example
Provider=Microsoft.ACE.OLEDB.12.0;Data Source=<path>;Extended Properties="Excel 12.0 Xml;HDR=YES;IMEX=1";). - Excel support is currently read-only in ASPPY (
SELECTqueries only). - For ODBC, use
DSN=...;Uid=...;Pwd=...;orDriver={...};Server=...;Database=...;Uid=...;Pwd=...;. - PostgreSQL is supported via ODBC (
Provider=PostgreSQL;Server=...;Database=...;User Id=...;Password=...;Port=5432;) or explicitDriver={PostgreSQL ...}/DSN=.... - SQL dialect translation is not performed; application SQL is executed as-is by the selected provider/driver.
- Internally, provider adapters now use a registry + capability flags, so future DBMS support can be added as pluggable adapters without changing the ADO surface API.
ADODB.Recordset
Set rs = Server.CreateObject("ADODB.Recordset")
| Property/Method | Description |
|---|---|
State | Recordset state |
EOF | End of file |
BOF | Beginning of file |
RecordCount | Number of records |
Fields | Fields collection |
Open([source[, active_conn[, cursor_type[, lock_type[, options]]]]]) | Opens recordset |
Close() | Closes recordset |
MoveFirst() | Moves to first record |
MoveLast() | Moves to last record |
MoveNext() | Moves to next record |
MovePrevious() | Moves to previous record |
Move(n) | Moves n records |
AddNew() | Starts insert mode for a new record |
Update() | Commits pending field changes |
Delete() | Deletes current record |
Resync | Resyncs with database |
GetRows([rows[, start[, fields]]]) | Rows as a 2-D array, field-major |
GetString([format[, rows[, colDelim[, rowDelim[, nullExpr]]]]]) | Rows as a delimited string — see below |
Bookmark | Marker for the current row; read it, move away, assign it back |
CompareBookmarks(a, b) | CompareEnum: 0 less, 1 equal, 2 greater, 4 not equal |
AbsolutePosition, PageSize, PageCount, AbsolutePage | Positioning and paging |
Filter, Sort, Find, Clone, Requery, Supports | Client-side cursor operations |
GetString
Reads forward from the current row and leaves the recordset at EOF (or just past the last row taken), as ADO does. Omitted arguments take the ADO defaults: TAB between columns, CR between rows, "" for Null. Every row is terminated, including the last.
' Pipe-separated, CRLF-terminated, empty string for NULL
gs = rs.GetString(, , "|", vbCrLf, "")
StringFormat only has one legal value, adClipString (2); anything else raises.
Bookmark
ASPPY recordsets are client-side cursors over an in-memory row list, so bookmarks are always available and are the row's ordinal. Assigning a bookmark outside the recordset raises rather than moving anywhere.
Divergence worth knowing: on IIS, bookmark support depends on the provider and cursor — a Jet OpenSchema recordset reports Supports(adBookmark) = False. ASPPY supports them everywhere, so code guarded by If rs.Supports(adBookmark) takes the enabled branch here and the disabled one there.
OpenSchema
Returns schema information as a Recordset, with the OLE DB rowset column names, so rs("TABLE_NAME") works as it does on IIS. Supported SchemaEnum values:
| Constant | Value | Columns |
|---|---|---|
adSchemaTables | 20 | TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, … |
adSchemaColumns | 4 | TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, IS_NULLABLE, DATA_TYPE, … |
adSchemaIndexes | 12 | INDEX_NAME, TABLE_NAME, UNIQUE, PRIMARY_KEY, COLUMN_NAME (SQLite only) |
adSchemaPrimaryKeys | 28 | PK_TABLE_NAME, COLUMN_NAME, ORDINAL (SQLite only) |
Set rs = conn.OpenSchema(20) ' every table
Set rs = conn.OpenSchema(20, Array(Empty, Empty, "widget")) ' one table
Set rs = conn.OpenSchema(4, Array(Empty, Empty, "widget")) ' its columns
criteria is an array of restriction values positionally matched to the rowset's restriction columns; Empty entries mean "no restriction". Any other queryType raises rather than returning an empty recordset. System tables are excluded.
ADODB.Field
Reached through rs.Fields(n), rs.Fields("name") or rs("name"). Its default property is Value, so a Field reads as its value in any expression.
| Property | Description |
|---|---|
Name, Value | Column name and current value |
Type | DataTypeEnum. SQLite reports no type in its cursor metadata, so it is inferred from the first non-NULL value in the column — SELECT 1 AS F1 gives adInteger (3), matching IIS |
DefinedSize | Declared size. For a fixed-width type this is the type's BYTE width (an adInteger column gives 4, as on IIS), otherwise the declared maximum |
ActualSize | Length in bytes of the stored value: the type width for fixed types, the character or byte count for variable ones, 0 for Null |
Attributes | FieldAttributeEnum. 114 for a nullable fixed-width column (adFldMayDefer | adFldFixed | adFldIsNullable | adFldMayBeNull), 98 for a variable-length one |
Status | FieldStatusEnum; adFieldOK (0) for a field read from a resultset |
OriginalValue, UnderlyingValue | The bound row's value. No separate pre-edit copy is kept, and there is no re-fetch, so both report the current row value |
Precision, NumericScale | Reported as 0; IIS gets these from the provider (10 and 255 for a Jet integer) |
Properties | Empty collection — see the note under ADODB.Command |
AppendChunk(data) | Appends to a long binary/text value |
ADODB.Command
Set cmd = Server.CreateObject("ADODB.Command")
| Property/Method | Description |
|---|---|
ActiveConnection | Connection used for execution |
CommandText | SQL command text |
CommandType | Command type (text) |
Parameters | Parameters collection |
CreateParameter(...) | Creates a parameter object |
Execute(...) | Executes command and returns a recordset |
State | ObjectStateEnum: adStateClosed (0) / adStateOpen (1) |
Properties | Provider property collection (empty, see below) |
Parameter objects returned by CreateParameter expose Name, Type, Direction, Size, Value, Precision, NumericScale, Attributes and Properties.
The Properties collections
Connection, Command, Recordset, Field and Parameter all expose a Properties collection so that obj.Properties.Count runs instead of raising error 438. It is always empty: those entries are OLE DB provider properties, and ASPPY talks to SQLite and ODBC directly rather than through a provider. IIS reports 14 on an unopened Connection, around 94 once it is opened against Jet, and 88 on a connected Command. Anything that enumerates them, or reads a specific provider property by name, will not find it.
ADODB.Parameter / Parameters
| Property/Method | Description |
|---|---|
Name | Parameter name |
Type | ADO type constant |
Direction | Direction (adParamInput, etc.) |
Size | Declared size |
Value | Parameter value |
Parameters.Append(param) | Adds parameter |
Parameters.Item(name_or_index) | Gets parameter |
Parameters.Count | Number of parameters |
ADODB.Stream
Set stream = Server.CreateObject("ADODB.Stream")
| Property/Method | Description |
|---|---|
Type | Stream type (1=Binary, 2=Text) |
Charset | Character set |
Position | Current position |
Size | Stream size |
EOS | End of stream |
State | Stream state |
LineSeparator | Line separator |
Mode | Open mode |
Open() | Opens stream |
Close() | Closes stream |
LoadFromFile(filename) | Loads from file |
SaveToFile(filename[, options]) | Saves to file |
Read([count]) | Reads bytes |
ReadText([count]) | Reads text |
Write(data) | Writes bytes (needs a Byte() array, not a string) |
WriteText(string[, options]) | Writes text. options is a StreamWriteEnum: adWriteChar (0, default) or adWriteLine (1), which appends LineSeparator |
CopyTo(dest_stream[, count]) | Copies to another stream |
SetEOS() | Makes the current position the end of the stream, discarding everything after it |
SkipLine() | Skips line |
Flush() | Flushes buffer |
Byte order marks, Size and Position
A text stream is measured in bytes, and ADO writes a byte order mark for the Unicode encodings. Verified against IIS by writing 16 characters plus one LF and reading the buffer back as binary:
Charset | Size | First bytes |
|---|---|---|
utf-8 | 20 | EF BB BF 48 — 3-byte BOM |
Unicode (UTF-16LE, the default) | 36 | FF FE 48 00 — 2-byte BOM |
windows-1252, iso-8859-1 | 17 | 48 65 6C 6C — no BOM |
The BOM appears only once there is content: an open but empty stream has Size 0. Size, Position, Read, CopyTo, SaveToFile and switching Type to binary all share the same byte view, so at end of stream Position equals Size, and the file written by SaveToFile is byte-for-byte what Read returns.
This matters in practice: ADODB.Stream plus SaveToFile is the usual way Classic ASP produces UTF-8 CSV and XML, and consumers — Excel above all — rely on the BOM being there.
Known limitation. ReadText and SkipLine still index the buffer by character internally. For single-byte charsets and for ASCII text that is indistinguishable from the byte offset; it can differ for multi-byte content in the middle of a stream.
MSXML2 Objects
ServerXMLHTTP
Set http = Server.CreateObject("MSXML2.ServerXMLHTTP")
| Property/Method | Description |
|---|---|
ReadyState | Request state |
Status | HTTP status code |
StatusText | HTTP status text |
ResponseText | Response as text |
ResponseXML | Response as XML DOM |
ResponseBody | Response as a Byte() SafeArray — see below |
Open(method, url[, async[, user[, password]]]) | Opens request |
SetRequestHeader(header, value) | Sets request header |
Send([body]) | Sends request |
SetTimeouts(resolve, connect, send, receive) | Receive timeout is used as the overall timeout |
SetProxy(setting[, server[, bypass]]) / SetProxyCredentials(user, pass) | Recorded; requests always go direct |
SetOption(option, value) / GetOption(option) | Recorded and readable. Option 2 (ignore SSL cert errors) is deliberately not honoured |
WaitForResponse([timeout]) | Waits for response (sends are synchronous, so always already complete) |
GetResponseHeader(name) / GetAllResponseHeaders() | Response headers |
Abort() | Aborts request |
Binary payloads are a Byte() SafeArray
ResponseBody, Request.BinaryRead and ADODB.Stream.Read return a byte array, matching IIS exactly:
| Expression | Result |
|---|---|
IsArray(b) | True |
TypeName(b) | "Byte()" |
VarType(b) | 8209 (vbArray + vbByte) |
IsObject(b) | False |
LBound(b) / UBound(b) | 0 / LenB(b) - 1 |
b(0) | the first byte, as an Integer |
objStream.Write http.ResponseBody and Response.BinaryWrite accept it unchanged.
XMLHTTP
Set http = Server.CreateObject("MSXML2.XMLHTTP")
Same interface as ServerXMLHTTP.
DOMDocument
Set xml = Server.CreateObject("MSXML2.DOMDocument")
| Property/Method | Description |
|---|---|
async | Async loading |
readyState | Document state |
xml | XML content |
text | Text content |
load(url) | Loads from URL |
loadXML(xml_string) | Loads from string |
save(destination) | Saves document |
selectNodes(xpath) | Selects nodes by XPath |
selectSingleNode(xpath) | Selects the first matching node, else Nothing |
getElementsByTagName(tagname) | Gets elements |
transformNode(xsl) / transformNodeToObject(xsl, out) | XSLT 1.0 transform (needs lxml) |
createElement, createTextNode, createCDATASection, createComment, createProcessingInstruction, createDocumentFragment, createEntityReference, createAttribute, createNode | Node factories, each reporting the MSXML nodeType (1, 3, 4, 8, 7, 11, 5, 2) |
baseName, prefix, namespaceURI, parsed, dataType, definition, specified | Present on the document and on element, attribute and text nodes |
getAttributeNode(name), setAttribute, removeAttribute | Attribute access on element nodes |
Node model notes
Nothing, notEmpty. A lookup that finds nothing —selectSingleNode,getAttributeNode,parentNodeat the document level — returnsNothing, so the standardIf Not n Is Nothing Thenguard works.childNodesincludes text. Text and CDATA appear as real nodes withnodeType3 and 4, sonode.firstChild.nodeValuereads an element's content andDo While n.nodeType = 3 : Set n = n.nextSibling : Loopterminates.- Fragments splice.
appendChild(fragment)inserts the fragment's children and discards the wrapper. save()writes anywhere inside the application root by default —xmlDoc.save Server.MapPath("out.xml")just works. Writing outside it requiresASP_PY_XML_ALLOW_LOCAL=1.
XPath, XSLT and the optional lxml package
Python's bundled XML parser has no XSLT engine and only a small XPath subset, so those features come from lxml, which is optional in the same way fpdf2, Pillow and bcrypt are:
pip install lxml
| Feature | With lxml | Without |
|---|---|---|
XPath: //a/b, [@x='y'], [n] | works | works |
XPath: namespace prefixes (//ns:Item), axes, text(), contains(), unions, absolute paths | works | raises, naming lxml |
transformNode / transformNodeToObject | works | raises, naming lxml |
| CDATA, comments and PIs survive load → save | yes | discarded on parse |
| Original namespace prefixes preserved on output | yes | rewritten as ns0: |
An unsupported XPath raises rather than returning an empty node list. Silently returning zero matches — which is what the stdlib parser does for anything outside its subset, including every namespaced document — is far harder to diagnose than an error.
Namespace prefixes in an XPath resolve from the document's own declarations, and from setProperty "SelectionNamespaces", "xmlns:a='urn:x'".
Deliberate divergences
- External DTDs are never fetched. MSXML downloads the DTD named in a
DOCTYPEand validates against it; ASPPY does not touch the network while parsing. A document MSXML rejects because the remote DTD failed to validate will parse here. Internal DTD subsets are accepted by both. - Entities are not expanded.
<!ENTITY x SYSTEM "file:///...">stays literal instead of inlining the file, so the classic XXE read does not work. - TLS verification cannot be turned off.
setOption 2(SXH_OPTION_IGNORE_SERVER_SSL_CERT_ERROR_FLAGS) is recorded and readable viagetOption, but certificate and hostname checks stay on. - Requests to localhost and private networks are blocked unless
ASP_PY_ALLOW_LOCALHOST=1/ASP_PY_ALLOW_PRIVATE_NETS=1. See Runtime Environment Variables.
WScript.Shell
Set wsh = Server.CreateObject("WScript.Shell")
| Property/Method | Description |
|---|---|
ExpandEnvironmentStrings(s) | Expands %NAME%. Unknown placeholders are preserved, as in WSH |
Environment([scope]) | SYSTEM / USER / VOLATILE / PROCESS collection, with Count, Length and Item |
CurrentDirectory | Read/write |
SpecialFolders | Collection; sf("Windows"), "System", "Fonts", "Temp", "Desktop", … An unknown or unavailable name yields "" rather than raising |
CreateShortcut(path) | .lnk → IWshShortcut, .url → IWshURLShortcut. Any other extension raises |
RegRead(name) | Reads a registry value (Windows only). HKLM/HKCU/HKCR/HKU/HKCC and their long forms; a trailing \ reads the key's default value. REG_MULTI_SZ comes back as an array, REG_BINARY as a Byte() |
Run, Exec | Not implemented — raise a catchable 429, see below |
IWshShortcut exposes TargetPath, Arguments, WorkingDirectory, Description, IconLocation, WindowStyle, Hotkey, FullName and Save. IWshURLShortcut exposes TargetPath, FullName and Save, and re-opening an existing .url reads its target back.
The .lnk writer builds a real [MS-SHLLINK] file — header, LinkTargetIDList, LinkInfo and the string blocks — rather than calling the Shell COM object, so the same code path runs on a non-Windows host. Shortcuts written by ASPPY were verified by reading them back through Windows' own WScript.Shell: every property round-trips and Explorer resolves the target. On a non-Windows host the LinkTargetIDList is omitted (it is produced by the Windows shell), which leaves a link Windows can parse but not resolve.
Deliberate divergences
RunandExecare not implemented. They execute arbitrary shell commands from a web request, which contradicts the sandboxing ASPPY applies elsewhere —FileSystemObjectis confined to the docroot, andADODB.StreamtoASP_PY_ADO_ROOT. They raise ActiveX error 429, whichOn Error Resume Nextcan detect, rather than failing silently.RegWriteandRegDeleteare not implemented for the same reason.RegReadis available because it cannot change machine state.
CDO.Message
Set msg = Server.CreateObject("CDO.Message")
| Property/Method | Description |
|---|---|
From | Sender address |
To | Recipient address(es) |
CC | CC recipients |
BCC | BCC recipients |
Subject | Message subject |
HTMLBody | HTML body |
TextBody | Plain text body |
BodyPart | Body part object |
Configuration | Configuration object |
DisableSend | If True, Send() is no-op success |
AddAttachment(url) | Adds attachment |
Send() | Sends the message |
ASPPY.POP3
Set pop = Server.CreateObject("ASPPY.POP3")
| Method | Description |
|---|---|
Connect/Open(host[, port[, use_ssl[, timeout]]]) | Connects to POP3 server |
Login(user, pass) | Authenticates |
Stat() | Returns message count and mailbox size |
List() | Returns message listing |
UIDL([msg_num]) | UID listing or UID for one message |
Retr/GetMessage(msg_num) | Fetches a message object |
Delete/Dele(msg_num) | Marks message for deletion |
DeleteAll() | Marks all for deletion |
Quit/Close() | Closes connection |
ASPPY.IMAP
Set imap = Server.CreateObject("ASPPY.IMAP")
| Method | Description |
|---|---|
Connect/Open(host[, port[, use_ssl[, timeout]]]) | Connects to IMAP server |
Login(user, pass) | Authenticates |
Select([folder[, readonly]]) | Selects mailbox |
Search([criteria]) | Finds messages by sequence number |
SearchUID([criteria]) | Finds messages by UID |
Fetch/GetMessage(msg_num) | Fetches message by sequence number |
GetMessageByUID(uid) | Fetches message by UID |
Delete/Dele(msg_num) | Marks message deleted |
DeleteAll() | Marks all selected messages deleted |
Expunge() | Permanently removes deleted messages |
Logout/Close() | Closes connection |
Runtime Environment Variables
| Variable | Description |
|---|---|
ASP_PY_LOG | Enables request log output from built-in server |
ASP_PY_TRACE_REQUEST | Enables verbose request/body trace logging |
ASP_PY_REQ_MEM_MAX | Max in-memory request body bytes before temp-file buffering |
ASP_PY_ADO_ROOT | Sandbox root for ADODB.Stream filesystem operations |
ASP_PY_FSO_ROOT | Sandbox root for Scripting.FileSystemObject |
ASP_PY_HTTP_MAX_BYTES | Max response bytes for MSXML HTTP requests |
ASP_PY_HTTP_ALLOW_HOSTS | Comma-separated HTTP host allowlist for MSXML |
ASP_PY_ALLOW_LOCALHOST | Allows MSXML HTTP access to localhost |
ASP_PY_ALLOW_PRIVATE_NETS | Allows MSXML HTTP access to private subnets |
ASP_PY_XML_ALLOW_LOCAL | Allows DOMDocument.load() from a local file, and save() outside the application root. Saving inside the root needs no flag. |
ASP_PY_CDO_DISABLE_SEND | Disables SMTP send in CDO.Message |
ASP_PY_CDO_ALLOW_OUTSIDE_DOCROOT | Allows CDO attachments outside docroot |
ASP_PY_ALLOW_PYTHON | Enables ASPPY.ExecutePython (set to 1) |
ASP_PY_PYTHON_TIMEOUT | Max seconds for ExecutePython snippets (default 30) |
ASP_PY_PYTHON_ROOT | Sandbox root for ExecutePythonFile |
Runtime Error Parity
Err.Number is the contract ASPPY holds itself to: it is checked case by case against a live IIS 10 / VBScript 10.8 installation, because it is locale-independent and it is what real code branches on (If Err.Number = 9 Then). Behaviours verified against IIS include:
| Expression | Err.Number | Meaning |
|---|---|---|
ReDim a(-1) | 0 | Legal. Creates an EMPTY dynamic array with UBound = -1, the same object Array() and Split("") return. This is the idiomatic empty growable list, and ReDim Preserve a(UBound(a) + 1) grows it. |
ReDim a(-2) | 7 | Out of memory. Only -1 is a valid empty upper bound; anything lower asks for a negative element count. |
Dim a(4) then ReDim a(9) | 10 | This array is fixed or temporarily locked. An array declared with explicit bounds cannot be resized - use Dim a(). |
Dim a() then UBound(a) | 9 | Subscript out of range. An un-ReDim'd dynamic array has no dimensions yet. (Contrast with ReDim a(-1) above.) |
obj + 1, obj & "", obj = 1 | 450 | Wrong number of arguments. An operator reads the object's DEFAULT property first; a Scripting.Dictionary's default is Item(key), which needs an argument. |
rs.Fields("id") + 1 | 0 | Works: ADODB.Field's default property is Value. Likewise Request.QueryString used without an index yields the raw query string, and Request.Cookies("x") yields the cookie value. |
| Operator on an object with no default property | 438 | Object doesn't support this property or method. |
CBool("xyz"), CBool("") | 13 | Type mismatch. CBool accepts only True/False or a number. Implicit truthiness (If "abc" Then) is separate and does not raise. |
Server.MapPath("") | -2147467259 | An empty path is rejected; it does not resolve to the application root. |
Err.Number is a signed 32-bit Long, so an HRESULT such as 0x80004005 reports as -2147467259 - never as 2147500037. This holds for Err.Raise &H80004005 too.
Deliberate divergences
Err.Descriptiontext. IIS localises descriptions - a Dutch server returns"Het subscript valt buiten het bereik"for error 9 - so there is no single IIS string to match. ASPPY always emits English, and appends diagnostic detail IIS omits (Subscript out of range: ARR(9), UBound = 2;Unknown member: SETMARGINZ on PdfDoc (did you mean 'SetMargins'?)). Branch onErr.Number, never on description text.Server.MapPath("../"). IIS resolves paths that escape the web root and returns them. ASPPY refuses, because on IIS the result is only safe thanks to filesystem ACLs that ASPPY cannot rely on. This is a hardening, and the one case where ASPPY is deliberately stricter than IIS.- Host-object internals. ASPPY's objects are Python classes, so they have members the COM originals do not. Every such member is prefixed with
_, which VBScript identifiers cannot name, soResponse.finalize_headersandSession.CodePage_raise error 438 exactly as they do on IIS.
VBScript Compatibility Notes
UBound/LBound: follow VBScript-like array allocation rules; uninitialized/invalid-array usage raises runtime errors. See Runtime Error Parity for the exactReDim/ empty-array rules.VBScript.RegExp.Replace: supports VB-style replacement tokens ($1,$&,$`,$',$$).Request.Form: parsed forPOSTonly; non-POST bodies are available viaRequest.BinaryRead.- Date/number formatting is locale-aware across 60 locales and verified against live IIS; see Localization for the supported list and the documented divergences.
Localization
ASPPY implements the Classic ASP locale model for 60 locales, driven by a generated table (ASPPY/locale_data.json) extracted from the Windows NLS data and validated field-by-field against live IIS output.
What is locale-aware
| Area | Behaviour |
|---|---|
FormatNumber, FormatCurrency, FormatPercent | Decimal/group separators, group size, currency symbol and its position, per-currency decimal digits (ja-JP renders ¥1,234,568, no decimals), and the locale negative/parenthesised patterns |
FormatDateTime | All five named formats from the locale's own patterns. vbShortTime is always 24-hour but uses the locale time separator |
MonthName, WeekdayName | Localised names, including the genitive month forms Slavic/Baltic/Finnic locales use in long dates (Polish 5 marca, not 5 marzec) |
Weekday, DatePart("w") | vbUseSystemDayOfWeek resolves to the locale first day - Monday across most of Europe, Saturday for ar-DZ |
CDbl, CCur, CInt, CLng, IsNumeric | Locale number parsing. CDbl("1,5") is 15 under en-US (comma = group separator) and 1.5 under nl-BE; fr-FR rejects "1.5" with a type mismatch, exactly as IIS does |
CDate, IsDate | Locale field order, so "3/5/2024" is March 5th under en-US and 5 March under nl-BE. A leading four-digit component is always read as ISO |
StrComp(a, b, vbTextCompare) | Locale collation rather than a lowercase compare: "straße" equals "strasse", and accents act as a tiebreak |
GetLocale / SetLocale | Numeric LCIDs and short names ("nl-be", "de") across all 60 locales |
Session.LCID, Response.LCID, <%@ LCID %> | All three seed the script engine locale. Session.LCID is re-applied at the start of every request in the session, as under IIS |
Deliberate divergences
- Default locale. With no locale set, ASPPY uses LCID 1033 (en-US) rather than inheriting the host's system locale, so output is identical on every machine. Same reasoning as the UTF-8 default below.
CStr(date)/ implicit date-to-string. While no locale has been selected, ASPPY keeps its ISO output (2024-03-05 14:07:09) instead of the IIS locale general date. This protects the very common pattern of concatenating a date straight into SQL. As soon as an app opts in viaSession.LCID,SetLocaleor<%@ LCID %>, full IIS formatting applies.- Non-Gregorian calendars. ar-SA and th-TH render dates on the Gregorian calendar, so their era and year differ from IIS (which uses Hijri and the Buddhist era). Separators, names, currency and number formatting are correct for both. All other 58 locales match IIS exactly.
CStr(True)returns"True". IIS localises boolean literals via the OS user locale, which is a property of the host rather than of the script.
Supported locales
en-US, en-GB, en-AU, en-CA · fr-FR, fr-BE, fr-CA, fr-CH · de-DE, de-CH, de-AT · nl-NL, nl-BE · es-ES (both sort orders), es-MX · it-IT, it-CH · pt-PT, pt-BR · ca-ES, gl-ES, eu-ES · sv-SE, sv-FI, da-DK, fi-FI, nb-NO, nn-NO, is-IS · pl-PL, cs-CZ, sk-SK, hu-HU, sl-SI, hr-HR, ro-RO, bg-BG, ru-RU, uk-UA, el-GR, tr-TR, et-EE, lv-LV, lt-LT · ja-JP, ko-KR, zh-CN, zh-TW, zh-HK · ar-SA, ar-DZ, he-IL, hi-IN, th-TH, id-ID, ms-MY, vi-VN, kk-KZ, uz-Latn-UZ.
Character Encoding
ASPPY defaults to UTF-8 (codepage 65001) everywhere. This is an intentional break from IIS.
Classic ASP under IIS inherits the system ANSI codepage of the host machine - typically 1252 on a Western Windows server, 932 on a Japanese one. ASPPY does not, for three reasons:
- ASPPY runs on Linux and macOS, where "the system ANSI codepage" has no meaningful equivalent - there is no coherent host default to inherit.
- Modern consumers (browsers,
fetch, JSON APIs, HTML5) assume UTF-8. - VBScript strings are Unicode internally. UTF-8 is the only encoding that round-trips them losslessly; legacy codepages silently substitute characters they cannot represent.
| Surface | ASPPY default | IIS default |
|---|---|---|
Session.CodePage / Response.CodePage | 65001 | system ANSI (e.g. 1252) |
Response.Charset | utf-8 | unset - header omits charset |
.asp source files | UTF-8, BOM-tolerant, falls back to cp1252 | BOM, else @CODEPAGE, else metabase |
Request.Form / Request.QueryString | decoded as UTF-8 | decoded per Session.CodePage |
Server.URLEncode | percent-encoded UTF-8 | percent-encoded per current codepage |
Migration impact
Pages served as windows-1252 under IIS are served as UTF-8 by ASPPY and labelled as such in the Content-Type header, so browsers render them correctly with no source change. Legacy .asp files saved in windows-1252 need no conversion - ASPPY detects and decodes them automatically.
Intervention is only required where a non-browser consumer expects legacy bytes: CSV exports opened in Excel, fixed-format files for banking or EDI partners, or older integration endpoints that assume iso-8859-1. Set the encoding explicitly on those responses:
Response.Charset = "windows-1252"
Compatibility Matrix
| Subsystem | Level | Notes |
|---|---|---|
| Character encoding / codepages | Intentional divergence | UTF-8 (65001) by default instead of the host ANSI codepage - see Character Encoding |
| Core VBScript built-ins | Near | Broad coverage for common Classic ASP apps; edge coercion behaviors can differ |
| Date/time and numeric formatting | Full | Verified against live IIS across 60 locales; only the ar-SA/th-TH calendars differ - see Localization |
| Localization (LCID model) | Full | 60 locales; Session.LCID, Response.LCID, <%@ LCID %>, Get/SetLocale, locale parsing and collation |
| Request/Response/Server/Application/Session | Near | Classic object model implemented; some operational semantics differ from IIS hosting internals |
VBScript.RegExp | Near | Common API and VB-style replacement tokens supported |
Scripting.Dictionary | Near | Core collection behavior supported; COM-level quirks may differ |
Scripting.FileSystemObject / ADODB.Stream | Partial | Supported in sandboxed model (docroot/root-constrained) |
ADODB.Connection/Recordset/Command | Partial | Provider support is runtime-dependent; ADO surface is compatibility-oriented |
| Database providers | Partial | SQLite, Access, Excel(read-only), generic ODBC, PostgreSQL via ODBC |
| MSXML HTTP/DOM shims | Partial | Security-guarded and stdlib-focused; not full MSXML COM parity |
CDO.Message | Partial | Practical SMTP/pickup subset; not complete CDOSYS feature parity |
| POP3/IMAP shims | Partial | Legacy-friendly subset for mailbox workflows |
ASPPY.ExecutePython | Full | Subprocess-based; full CPython access with ASPPY_RETURN convention |
Global.asa Support
ASPPY supports the Global.asa file with the following events:
Application_OnStartApplication_OnEndSession_OnStartSession_OnEnd
Example Global.asa:
<script language="vbscript" runat="server">
Sub Application_OnStart
Application("StartTime") = Now()
End Sub
Sub Session_OnStart
Session("UserID") = ""
End Sub
</script>
Running ASPPY
Start the built-in server:
python -m ASPPY.server [host] [port] [docroot]
Example:
python -m ASPPY.server 0.0.0.0 8080 www
The server will serve both static files and .asp pages from the specified document root.