Main Menu

Recent posts

#1
Open Sage 100 Overview / Open Sage 100 Design Concepts
Last post by root - Jun 07, 2026, 10:33 AM
Open Sage 100 is an open source project creating a work-a-like accounting solution with a web browser UI using the ScriptBasic webserver.

A three tier architecture will be maintained using MODULEs instead of ProvideX emulated objects. This eliminates the need for COM and BOI scripting. OS100 will have a REST API for external interfacing. UI events from the Bootstrap framework UI will be handled as Fetch API calls.

MODULEs will focus on business logic, not legacy support.

Open Sage 100 is targeted for Linux but can run on a Windows server or on a standalone PC.

I'm going to do an AR_Customer MODULE as an example to show the differences between ProvideX and ScriptBasic.
#2
Introduction / Sage 100 Web
Last post by root - Jun 02, 2026, 08:42 PM
Once I get the internal ScriptBasic BOI working and a 64 bit version of the COM extension compiled, I'm going to create a new category for a Sage 100 work-a-like for the web.

When Stephen Kelly was CEO of Sage, he gave me a contract to do a proof of concept of Sage 100 running as a web browser application with ScriptBasic. The project died when he left abruptly.

PeovideX objects will become ScriptBasic MODULEs and the UI will be done with the Bootstrap framework with form templates.

The ScriptBasic webserver has a built-in memory based session management system with session variables. The ScriptBasic webserver is multi-threaded with it own thread safe memory management environment.

Interm, Sage 100 Hybrid will be the bridge.

The key features of Open Sage 100 is unlimited users and everyone can be a master developer with its open source.
#3
BOI Overview / Internal BOI
Last post by root - May 27, 2026, 08:52 PM
Working with Chris Mattson, I think I've found a compatible solution for using ScriptBasic embedded for internal BOI scripting.

When using Custom Office to create your script, write it in ScriptBasic COM and save the script with a .sb file type attribute. This is simular to using WSH .js scripts.

This will embed ScriptBasic as the scripting engine instead of instantiating MSScriptControl.ScriptControl.

I will post an example after doing some testing.
#4
Introduction / Hi all
Last post by Chris Mattson - May 26, 2026, 08:10 AM
Just checking in to introduce myself.  I'm a Sage 100 developer from way back in the v1.41 days when Sage 100 was a character app in BBx2.0.  I currently work for Nuvei integrating Sage 100 with Nuvei's Commerce Suite platform.
#5
BOI Overview / Excel COM
Last post by root - May 25, 2026, 05:46 PM
This is an example of using the ScriptBasic COM extension module to create an Excel spreadsheet workbook without human interaction. The example pops the DI (Display Interface) to show what the WorkBook object interface offers.

IMPORT COM.sbi

filename = "C:\\ScriptBASIC\\examples\\excel_test.xls"

IF FileExists(filename) THEN
  PRINT "File already exists deleting: ", filename,"\n"
  DELETE filename
END IF

oExcelApp = COM::CREATE(:SET, "Excel.Application")
' oExcelApp = COM::CREATE(:SET, "{00024500-0000-0000-C000-000000000046}")

IF oExcelApp = 0 THEN
  PRINT "Failed to create Excel Object do you have it installed?\n"
  END
END IF

' VBS: Set ExcelWorkbook = ExcelApp.Workbooks.Add
oWorkBook = COM::CBN(oExcelApp, "Workbooks", :GET)

IF oWorkBook = 0 THEN
  PRINT "Failed to create Workbook\n"
  END
END IF

oExcelWorkbook = COM::CBN(oWorkBook, "Add")
COM::DI(oExcelWorkBook)
' VBS: Set ExcelSheet = ExcelWorkbook.Worksheets(1)
oExcelSheet = COM::CBN(oExcelWorkbook, "Worksheets", :GET, 1)

IF oExcelSheet = 0 THEN
  PRINT "Failed to get oExcelSheet\n"
  END
END IF

' Adding cells...

oRange =  COM::CBN(oExcelSheet, "Range", :GET, "G3")

IF oRange = 0 THEN
  PRINT "Failed to get oRange\n"
  END
END IF

COM::CBN(oRange, "Value", :LET, "123")
COM::RELEASE(oRange)

oRange =  COM::CBN(oExcelSheet, "Range", :GET, "B1:B5")

IF oRange = 0 THEN
  PRINT "Failed to get oRange\n"
  END
END IF

' Place BOLD border around range
CONST XlBorderWeight_xlMedium = -4138
COM::CBN(oRange, "BorderAround", :CALL, 1, XlBorderWeight_xlMedium, 3)

oInterior = COM::CBN(oRange, "Interior", :GET)

IF oInterior = 0 THEN
  PRINT "Failed to get oInterior\n"
  END
END IF 

COM::CBN(oInterior, "ColorIndex", :LET, "38")
COM::CBN(oInterior, "Pattern", :LET, "xlSolid")

COM::RELEASE(oRange)
COM::RELEASE(oInterior)

FOR i=1 TO 10
  FOR j=1 TO 10
'   VBS: ExcelSheet.Cells(i, j).Value = "test-" & i & "-" & j
    oCell = COM::CBN(oExcelSheet, "Cells", :GET, i, j)
    COM::CBN(oCell, "Value", :LET, "test-" & i & "-" & j)
    COM::RELEASE(oCell)
  NEXT
NEXT

' Saving spreadsheet
COM::CBN(oExcelWorkbook, "SaveAs", :CALL, filename)
COM::CBN(oExcelWorkbook, "Close")
COM::CBN(oExcelApp, "Quit")

' Releasing objects from memory
COM::RELEASE(oExcelSheet)
COM::RELEASE(oExcelWorkbook)
COM::RELEASE(oWorkBook)
COM::RELEASE(oExcelApp)

PRINT "Spreadsheet Created.\n"

Output:

C:\ScriptBasic\examples>sbc excel.sb
Spreadsheet Created.

C:\ScriptBasic\examples>
#6
Language Features / MySQL Entension Module
Last post by root - May 23, 2026, 06:31 PM
ScriptBasic supports MySQL with a C API interface that is lightening fast. I don't have the MySQL Classic  Models example DB on my Windows laptop so these examples will be from my Linux servers.

' MySQL Test Program

IMPORT mysql.bas

dbh = mysql::RealConnect("localhost","root","PASSWORD","classicmodels")

mysql::query(dbh,"SELECT * FROM products LIMIT 25")

WHILE mysql::FetchHash(dbh,column)
  PRINT column{"productCode"}," - ",column{"productName"}," - ",FORMAT("%~$###.00~",column{"MSRP"}),"\n"
WEND

PRINTNL
PRINT "The database handle is: ",dbh,"\n"
PRINT "Affected rows by SELECT: ",mysql::AffectedRows(dbh),"\n"
PRINT "Character set name is: ",mysql::CharacterSetName(dbh),"\n"
PRINT "Last error is: ",mysql::ErrorMessage(dbh),"\n"
PRINT "Client info is: ",mysql::GetClientInfo(),"\n"
PRINT "Host info is: ",mysql::GetHostInfo(dbh),"\n"
PRINT "Proto info is: ",mysql::GetProtoInfo(dbh),"\n"
PRINT "Server info is: ",mysql::GetServerInfo(dbh),"\n"
PRINT "PING result: ",mysql::Ping(dbh),"\n"
PRINT "Thread ID: ",mysql::ThreadId(dbh),"\n"
PRINT "Status is: ",mysql::Stat(dbh),"\n"

mysql::Close(dbh)

Output:
rs@linux-dev:~/sbrt/examples$ sbc testmysql.sb
S10_1678 - 1969 Harley Davidson Ultimate Chopper - $ 95.70
S10_1949 - 1952 Alpine Renault 1300 - $214.30
S10_2016 - 1996 Moto Guzzi 1100i - $118.94
S10_4698 - 2003 Harley-Davidson Eagle Drag Bike - $193.66
S10_4757 - 1972 Alfa Romeo GTA - $136.00
S10_4962 - 1962 LanciaA Delta 16V - $147.74
S12_1099 - 1968 Ford Mustang - $194.57
S12_1108 - 2001 Ferrari Enzo - $207.80
S12_1666 - 1958 Setra Bus - $136.67
S12_2823 - 2002 Suzuki XREO - $150.62
S12_3148 - 1969 Corvair Monza - $151.08
S12_3380 - 1968 Dodge Charger - $117.44
S12_3891 - 1969 Ford Falcon - $173.02
S12_3990 - 1970 Plymouth Hemi Cuda - $ 79.80
S12_4473 - 1957 Chevy Pickup - $118.50
S12_4675 - 1969 Dodge Charger - $115.16
S18_1097 - 1940 Ford Pickup Truck - $116.67
S18_1129 - 1993 Mazda RX-7 - $141.54
S18_1342 - 1937 Lincoln Berline - $102.74
S18_1367 - 1936 Mercedes-Benz 500K Special Roadster - $ 53.91
S18_1589 - 1965 Aston Martin DB5 - $124.44
S18_1662 - 1980s Black Hawk Helicopter - $157.69
S18_1749 - 1917 Grand Touring Sedan - $170.00
S18_1889 - 1948 Porsche 356-A Roadster - $ 77.00
S18_1984 - 1995 Honda Civic - $142.25

The database handle is: 1
Affected rows by SELECT: 25
Character set name is: utf8mb4
Last error is:
Client info is: 8.0.44
Host info is: Localhost via UNIX socket
Proto info is: 10
Server info is: 8.0.44-0ubuntu0.24.04.2
PING result: -1
Thread ID: 0
Status is: Uptime: 837  Threads: 2  Questions: 1039  Slow queries: 0  Opens: 470  Flush tables: 3  Open tables: 389  Queries per second avg: 1.241
jrs@linux-dev:~/sbrt/examples$

THIS MySQL example is using AJAX to select the product line to display.This is running on the ScriptBasic web server.

' ScriptBasic AJAX & MySQL

IMPORT cgi.bas

cgi::Header 200,"text/html"
cgi::FinishHeader

PRINT """
<html>
<head>
<script>
function showItems(str) {
  if (str == "") {
    document.getElementById("results").innerHTML = "";
    return;
  } else {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        document.getElementById("results").innerHTML = this.responseText;
      }
    };
    xmlhttp.open("GET","/home/qbo/getitems.sb?q="+str,true);
    xmlhttp.send();
  }
}
</script>
</head>
<body>

<form>
  <select name="items" onchange="showItems(this.value)">
    <option value="">Product Line</option>
    <option value="Classic Cars">Classic Cars</option>
    <option value="Motorcycles">Motorcycles</option>
    <option value="Planes">Planes</option>
    <option value="Ships">Ships</option>
    <option value="Trains">Trains</option>
    <option value="Trucks and Buses">Trucks and Buses</option>
    <option value="Vintage Cars">Vintage Cars</option>
  </select>
</form>
<br>
<div id="results"></div>

</body>
</html>
"""

' AJAX - getitems.sb

IMPORT cgi.bas
IMPORT mysql.bas

cgi::Header 200,"text/html"

PRINT """
<!DOCTYPE html>
<html>
<head>
<style>
table {
  width: 100%;
  border-collapse: collapse;
}

table, td, th {
  border: 1px solid black;
  padding: 5px;
}

th {text-align: left;}
</style>
</head>
<body>
"""

product_line = cgi::GetParam("q")

dbh = mysql::RealConnect("localhost","USER","PASSWORD","classicmodels")
mysql::query(dbh,"SELECT * FROM products WHERE productLine = '" & product_line & "'")

PRINT """
<table>
  <tr>
    <th>Product Code</th>
    <th>Product Line</th>
    <th>Product Vendor</th>
    <th>Product Name</th>
    <th>In Stock</th>
    <th>Cost</th>
    <th>MSRP</th>
  </tr>
"""

WHILE mysql::FetchHash(dbh,column)
  PRINT "  <tr>\n"
  PRINT "    <td>", column{"productCode"}, "</td>\n"
  PRINT "    <td>", column{"productLine"}, "</td>\n"
  PRINT "    <td>", column{"productVendor"}, "</td>\n"
  PRINT "    <td>", column{"productName"}, "</td>\n"
  PRINT "    <td align=\"right\">", column{"quantityInStock"}, "</td>\n"
  PRINT "    <td align=\"right\">", FORMAT("%~$###.00~",column{"buyPrice"}), "</td>\n"
  PRINT "    <td align=\"right\">", FORMAT("%~$###.00~",column{"MSRP"}), "</td>\n"
  PRINT "  </tr>\n"
WEND

PRINT """
</table>
</body>
</html>
"""

mysql::Close(dbh)
#7
Sponsor List / How to become a sponsor.
Last post by root - May 22, 2026, 06:01 PM
The Open Sage and ScriptBasic projects are hosted and managed by John Spikowski in my spare time. If you would like to sponsor these projects, the Buy me a coffee site manages sponsor monthly commitments.

Gold Sponsor

This level sponsor gets their own forum board with moderation privileges. I will be available for e-mail support for ScriptBasic / BOI questions. The monthly sponsorship commitment is $500.

Silver Sponsor

This level sponsor shares a board with other silver sponsors and moderated by the board administrator. The monthly sponsorship commitment is $100.

User Appreciation

Contributions made by users of the software provided here will be kept in post of user contributions. Give what you can to help the project grow and be useful for the Sage 100 community.

Note:

Only Sponsor members may advertise their services in the Sponsor category. All other members are Open Sage project contributors.





#8
BOI Overview / SBT Embedding
Last post by root - May 19, 2026, 07:11 PM
ScriptBasic can be embedded into other applications as a DLL interface. I wrote the SBT extension module which embeds ScriptBasic in ScriptBasic to help others with the ScriptBasic API. I also added the ability to embed ScriptBasic as a thread.

The following example demonstrates accessing the ScriptBasic API

' SBT Demo

IMPORT sbt.sbi

sb_code = """
FUNCTION prtvars(a, b, c)
  PRINT a,"\\n"
  PRINT FORMAT("%g\\n", b)
  PRINT c,"\\n"
  prtvars = "Function Return"
END FUNCTION

a = 0
b = 0
c = ""
"""

sb = SB_New()
SB_Configure sb, "C:/Windows/SCRIBA.INI_32"
SB_Loadstr sb, sb_code
SB_NoRun sb
' Call function before running script
funcrtn = SB_CallSubArgs(sb,"main::prtvars", 123, 1.23, "One,Two,Three")
PRINT funcrtn,"\n"
' Run script initializing globals
SB_Run sb, ""
' Assign variables values
SB_SetInt sb, "main::a", 321
SB_SetDbl sb, "main::b", 32.1
SB_SetStr sb, "main::c", "Three,Two,One" & CHR(0)
' Call function again with variables assigned in the previous step
SB_CallSubArgs sb, "main::prtvars", _
          SB_GetVar(sb, "main::a"), _
          SB_GetVar(sb, "main::b"), _
          SB_GetVar(sb, "main::c")
SB_Destroy sb

Output

C:\ScriptBasic\examples>sbc sbt_demo.sb
123
1.23
One,Two,Three
Function Return
321
32.1
Three,Two,One

C:\ScriptBasic\examples>


This is an example of embedding ScriptBasic as a thread process and using the MT extension module to share a common status variable among processes.

Code (main) Select

' SBT Main

IMPORT mt.sbi
IMPORT sbt.sbi

SB_ThreadStart("sbt_thread.sb", "","C:/Windows/SCRIBA.INI")

FOR x = 1 TO 10
  PRINT "M:",x,"\n"
  sb_msSleep(20)
NEXT

SB_msSleep(1000)

PRINT "Thread ",mt::GetVariable("thread_status"),"\n"

Code (thread) Select
' SBT Thread

IMPORT mt.sbi
IMPORT sbt.sbi

FOR x = 1 TO 10
  PRINT "T:",x,"\n"
  SB_msSleep(20)
NEXT

mt::SetVariable "thread_status","Completed"

SB_ThreadEnd

Output

C:\ScriptBasic\examples>sbc sbt_main.sb
T:1
T:2
M:1
T:3
M:2
M:3
T:4
M:4
T:5
M:5
T:6
T:7
M:6
M:7
T:8
T:9
M:8
M:9
T:10
M:10
Thread Completed

C:\ScriptBasic\examples>
#9
Language Features / cURL Internet Access
Last post by root - May 17, 2026, 04:59 PM
ScriptBasic provides a C API extension module for libcurl which offers HTML, REST and GraphQL direct access from your BOI scripts. This eliminates the need for third party emulations like ROI In-Synch.

This cURL example does a REST call to the Open Weather service and converts the JSON response to a ScriptBasic associative array. It displays the raw JSON and as a ScriptBasic array using the sbadump() debug function.

' OpenWeather - cURL & JSON Example
 
IMPORT curl.sbi
IMPORT webext.sbi
 
place = COMMAND()
 
ch = curl::init()
curl::option(ch, "URL", "http://api.openweathermap.org/data/2.5/weather?q=" & place & "&units=imperial&appid=APP KEY")
curl::option(ch, "CUSTOMREQUEST", "GET")
json = curl::perform(ch)
curl::finish(ch)
PRINT json,"\n\n"
web::json2sba(json)
web::sbadump(json)

PRINT "\nTempreture: ", json{"main"}{"temp"}, " F\n"
PRINT "Date: ", FORMATDATE("MM/DD/YEAR 0H:0m:0s",json{"dt"} + json{"timezone"}),"\n"

END

Output:

C:\ScriptBasic\examples>sbc weather.sb Lynden
{"coord":{"lon":-122.4521,"lat":48.9465},"weather":[{"id":801,"main":"Clouds","description":"few clouds","icon":"02d"}],"base":"stations","main":{"temp":65.07,"feels_like":63.77,"temp_min":60.51,"temp_max":67.69,"pressure":1023,"humidity":53,"sea_level":1023,"grnd_level":1015},"visibility":10000,"wind":{"speed":11.5,"deg":240},"clouds":{"all":20},"dt":1779061311,"sys":{"type":2,"id":2043940,"country":"US","sunrise":1779020706,"sunset":1779076053},"timezone":-25200,"id":5802035,"name":"Lynden","cod":200}

coord
  lon = -122.452
  lat = 48.9465
weather
  [1]
      id = 801
      main = Clouds
      description = few clouds
      icon = 02d
base = stations
main
  temp = 65.07
  feels_like = 63.77
  temp_min = 60.51
  temp_max = 67.69
  pressure = 1023
  humidity = 53
  sea_level = 1023
  grnd_level = 1015
visibility = 10000
wind
  speed = 11.5
  deg = 240
clouds
  all = 20
dt = 1779061311
sys
  type = 2
  id = 2043940
  country = US
  sunrise = 1779020706
  sunset = 1779076053
timezone = -25200
id = 5802035
name = Lynden
cod = 200

Tempreture: 65.07 F
Date: 5/17/2026 16:41:51

C:\ScriptBasic\examples>
#10
Language Features / Re: ScriptBasic Syntax
Last post by root - May 16, 2026, 06:49 PM
FORMAT

The FORMAT function allows creating a formatted string with constants and variables. This uses the C style format stucture.

PRINT FORMAT("Today is %s %ith %i\n", "May", 16, 2026)

Output:

Today is May 16th 2026

To format a number based on a mask, the following statement can be used,

PRINT FORMAT("%~$##,##0.00-~", 1234),"\n"

Output:

$ 1,234.00

Date and Time

All time and date math is done by seconds past 1/1/1970.

NOW() returns the the current time based on timezone in seconds.

current_time = TIMEVALUE(year,month,day,hours,minutes,seconds)

PRINT TIMEVALUE(2026,5,16,18,25,0), "\n"

Output:

1778955900

GMTIME() returns the time in seconds but based on GMT time.

YEARDAY returns the day number of the year.

PRINT YEARDAY(NOW),"\n"

Output:

135

date_str = FORMATDATE("format",time)

     YEAR         four digit year
     YY           two digit year
     MON          three letter abbreviation of the month name
     MM           month
     0M           month with leading zero if needed
     *MONTH-NAME* name of the month
     DD           day of the month
     0D           day of the month with leading zero if needed
     WD           week day on a single digit starting with sunday=0
     WEEKDAY-NAME the name of the weekday
     WDN          three letter abbreviation fo the week day name
     HH           hours (24 hours notation)
     0H           hours with leading zero if needed (24 hours notation)
     hh           hours (12 hours notation)
     0h           hours with leading zero if needed (12 hours notation)
     mm           minutes
     0m           minutes with leading zero if needed
     am / pm      is am or pm

Any other characters in the format string will be retuened verbatim.

results = ADDDAY(NOW,7)  - This adds 7 days to the current time.

There are similar functions for year, month, hour, minute and seconds.