-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathserverlist.go
More file actions
121 lines (112 loc) · 2.93 KB
/
serverlist.go
File metadata and controls
121 lines (112 loc) · 2.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
package sqlcmd
import (
"context"
"errors"
"fmt"
"io"
"net"
"os"
"sort"
"strings"
"time"
"github.com/microsoft/go-mssqldb/msdsn"
)
// ListLocalServers queries the SQL Browser service for available SQL Server instances
// and writes the results to the provided writer.
func ListLocalServers(w io.Writer) {
instances, err := GetLocalServerInstances()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
for _, s := range instances {
_, _ = fmt.Fprintf(w, " %s\n", s)
}
}
// GetLocalServerInstances queries the SQL Browser service and returns a list of
// available SQL Server instances on the local machine.
// Returns an error for non-timeout network errors.
func GetLocalServerInstances() ([]string, error) {
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 nil, nil
}
defer func() { _ = conn.Close() }()
dl, _ := ctx.Deadline()
_ = conn.SetDeadline(dl)
_, err = conn.Write(bmsg)
if err != nil {
// Only return error if it's not a timeout
if !errors.Is(err, os.ErrDeadlineExceeded) {
return nil, err
}
return nil, nil
}
read, err := conn.Read(resp)
if err != nil {
// Only return error if it's not a timeout
if !errors.Is(err, os.ErrDeadlineExceeded) {
return nil, err
}
return nil, nil
}
data := parseInstances(resp[:read])
instances := make([]string, 0, len(data))
// Sort instance names for deterministic output
instanceNames := make([]string, 0, len(data))
for s := range data {
instanceNames = append(instanceNames, s)
}
sort.Strings(instanceNames)
for _, s := range instanceNames {
serverName := data[s]["ServerName"]
if serverName == "" {
// Skip instances without a ServerName
continue
}
if s == "MSSQLSERVER" {
instances = append(instances, "(local)", serverName)
} else {
instances = append(instances, fmt.Sprintf(`%s\%s`, serverName, s))
}
}
return instances, nil
}
func parseInstances(msg []byte) msdsn.BrowserData {
results := msdsn.BrowserData{}
if len(msg) > 3 && msg[0] == 5 {
outStr := string(msg[3:])
tokens := strings.Split(outStr, ";")
instanceDict := map[string]string{}
gotName := false
var name string
for _, token := range tokens {
if gotName {
instanceDict[name] = token
gotName = false
} else {
name = token
if len(name) == 0 {
if len(instanceDict) == 0 {
break
}
// Only add if InstanceName key exists and is non-empty
if instName, ok := instanceDict["InstanceName"]; ok && instName != "" {
results[strings.ToUpper(instName)] = instanceDict
}
instanceDict = map[string]string{}
continue
}
gotName = true
}
}
}
return results
}