Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,41 @@ switches are most important to you to have implemented next in the new sqlcmd.
- `:Connect` now has an optional `-G` parameter to select one of the authentication methods for Azure SQL Database - `SqlAuthentication`, `ActiveDirectoryDefault`, `ActiveDirectoryIntegrated`, `ActiveDirectoryServicePrincipal`, `ActiveDirectoryManagedIdentity`, `ActiveDirectoryPassword`. If `-G` is not provided, either Integrated security or SQL Authentication will be used, dependent on the presence of a `-U` username parameter.
- The new `--driver-logging-level` command line parameter allows you to see traces from the `go-mssqldb` client driver. Use `64` to see all traces.
- Sqlcmd can now print results using a vertical format. Use the new `--vertical` command line option to set it. It's also controlled by the `SQLCMDFORMAT` scripting variable.
- `:help` displays a list of available sqlcmd commands.
- `:serverlist` lists local SQL Server instances discovered via the SQL Server Browser service (UDP port 1434). The command queries the SQL Browser service and displays the server name and instance name for each discovered instance. If no instances are found or the Browser service is not running, no output is produced. Non-timeout errors are printed to stderr.

```
1> :serverlist
MYSERVER\SQL2019
MYSERVER\SQL2022
Comment on lines +162 to +163
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example output shown here does not include the two-space indentation that the actual implementation produces. The code in ListLocalServers (serverlist.go:28) formats the output as " %s\n" with two leading spaces. The documentation should accurately show this formatting or the code should be updated to match the documentation.

Suggested change
MYSERVER\SQL2019
MYSERVER\SQL2022
MYSERVER\SQL2019
MYSERVER\SQL2022

Copilot uses AI. Check for mistakes.
Comment on lines +162 to +163
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example output doesn't show the behavior when the default MSSQLSERVER instance is present. According to the code (serverlist.go:82-83), the default instance produces two entries in the output: "(local)" and the actual server name (e.g., "MYSERVER"). Consider adding an example showing this case, such as " (local)\n MYSERVER\n MYSERVER\SQL2019".

Suggested change
MYSERVER\SQL2019
MYSERVER\SQL2022
(local)
MYSERVER
MYSERVER\SQL2019

Copilot uses AI. Check for mistakes.
```

#### Using :serverlist in batch scripts

When automating server discovery, you can capture the output and check for errors:

```batch
@echo off
REM Discover local SQL Server instances and connect to the first one
sqlcmd -Q ":serverlist" 2>nul > servers.txt
if %errorlevel% neq 0 (
echo Error discovering servers
exit /b 1
)
for /f "tokens=1" %%s in (servers.txt) do (
echo Connecting to %%s...
sqlcmd -S %%s -Q "SELECT @@SERVERNAME"
goto :done
)
echo No SQL Server instances found
:done
```

To capture stderr separately (for error logging):
```batch
sqlcmd -Q ":serverlist" 2>errors.log > servers.txt
if exist errors.log if not "%%~z errors.log"=="0" type errors.log
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This batch script has a syntax error. The file size test operator %~z only works within a FOR loop context, not directly in an IF statement. A correct alternative would be to use FOR /F to check the file size, or simply check if the file exists and has content using: "if exist errors.log (for %%A in (errors.log) do if %%~zA GTR 0 type errors.log)"

Suggested change
if exist errors.log if not "%%~z errors.log"=="0" type errors.log
if exist errors.log (for %%A in (errors.log) do if %%~zA GTR 0 type errors.log)

Copilot uses AI. Check for mistakes.
```

```
1> select session_id, client_interface_name, program_name from sys.dm_exec_sessions where session_id=@@spid
Expand Down
79 changes: 1 addition & 78 deletions cmd/sqlcmd/sqlcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@
package sqlcmd

import (
"context"
"errors"
"fmt"
"net"
"os"
"regexp"
"runtime/trace"
"strconv"
"strings"
"time"

mssql "github.com/microsoft/go-mssqldb"
"github.com/microsoft/go-mssqldb/azuread"
"github.com/microsoft/go-mssqldb/msdsn"
"github.com/microsoft/go-sqlcmd/internal/localizer"
"github.com/microsoft/go-sqlcmd/pkg/console"
"github.com/microsoft/go-sqlcmd/pkg/sqlcmd"
Expand Down Expand Up @@ -236,7 +232,7 @@ func Execute(version string) {
fmt.Println()
fmt.Println(localizer.Sprintf("Servers:"))
}
listLocalServers()
sqlcmd.ListLocalServers(os.Stdout)
os.Exit(0)
}
if len(argss) > 0 {
Expand Down Expand Up @@ -911,76 +907,3 @@ func run(vars *sqlcmd.Variables, args *SQLCmdArguments) (int, error) {
s.SetError(nil)
return s.Exitcode, err
}

func listLocalServers() {
bmsg := []byte{byte(msdsn.BrowserAllInstances)}
resp := make([]byte, 16*1024-1)
dialer := &net.Dialer{}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
conn, err := dialer.DialContext(ctx, "udp", ":1434")
// silently ignore failures to connect, same as ODBC
if err != nil {
return
}
defer conn.Close()
dl, _ := ctx.Deadline()
_ = conn.SetDeadline(dl)
_, err = conn.Write(bmsg)
if err != nil {
if !errors.Is(err, os.ErrDeadlineExceeded) {
fmt.Println(err)
}
return
}
read, err := conn.Read(resp)
if err != nil {
if !errors.Is(err, os.ErrDeadlineExceeded) {
fmt.Println(err)
}
return
}

data := parseInstances(resp[:read])
instances := make([]string, 0, len(data))
for s := range data {
if s == "MSSQLSERVER" {

instances = append(instances, "(local)", data[s]["ServerName"])
} else {
instances = append(instances, fmt.Sprintf(`%s\%s`, data[s]["ServerName"], s))
}
}
for _, s := range instances {
fmt.Println(" ", s)
}
}

func parseInstances(msg []byte) msdsn.BrowserData {
results := msdsn.BrowserData{}
if len(msg) > 3 && msg[0] == 5 {
out_s := string(msg[3:])
tokens := strings.Split(out_s, ";")
instdict := map[string]string{}
got_name := false
var name string
for _, token := range tokens {
if got_name {
instdict[name] = token
got_name = false
} else {
name = token
if len(name) == 0 {
if len(instdict) == 0 {
break
}
results[strings.ToUpper(instdict["InstanceName"])] = instdict
instdict = map[string]string{}
continue
}
got_name = true
}
}
}
return results
}
Loading
Loading