mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-13 08:26:37 +00:00
Add terminal service session info (#1525)
This commit is contained in:
@@ -87,6 +87,8 @@ func NewWithFlags(app *kingpin.Application) Collectors {
|
||||
}
|
||||
|
||||
// NewWithConfig To be called by the external libraries for collector initialization without running kingpin.Parse
|
||||
//
|
||||
//goland:noinspection GoUnusedExportedFunction
|
||||
func NewWithConfig(logger log.Logger, config Config) Collectors {
|
||||
collectors := map[string]types.Collector{}
|
||||
collectors[ad.Name] = ad.New(logger, &config.Ad)
|
||||
@@ -145,6 +147,7 @@ func NewWithConfig(logger log.Logger, config Config) Collectors {
|
||||
collectors[time.Name] = time.New(logger, &config.Time)
|
||||
collectors[vmware.Name] = vmware.New(logger, &config.Vmware)
|
||||
collectors[vmware_blast.Name] = vmware_blast.New(logger, &config.VmwareBlast)
|
||||
|
||||
return New(collectors)
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,8 @@ type Config struct {
|
||||
}
|
||||
|
||||
// ConfigDefaults Is an interface to be used by the external libraries. It holds all ConfigDefaults form all collectors
|
||||
//
|
||||
//goland:noinspection GoUnusedGlobalVariable
|
||||
var ConfigDefaults = Config{
|
||||
Ad: ad.ConfigDefaults,
|
||||
Adcs: adcs.ConfigDefaults,
|
||||
|
||||
@@ -242,7 +242,7 @@ func (c *collector) collectRemoteFXNetworkCount(ctx *types.ScrapeContext, ch cha
|
||||
|
||||
for _, d := range dst {
|
||||
// only connect metrics for remote named sessions
|
||||
n := strings.ToLower(d.Name)
|
||||
n := strings.ToLower(normalizeSessionName(d.Name))
|
||||
if n == "" || n == "services" || n == "console" {
|
||||
continue
|
||||
}
|
||||
@@ -250,81 +250,81 @@ func (c *collector) collectRemoteFXNetworkCount(ctx *types.ScrapeContext, ch cha
|
||||
c.BaseTCPRTT,
|
||||
prometheus.GaugeValue,
|
||||
utils.MilliSecToSec(d.BaseTCPRTT),
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.BaseUDPRTT,
|
||||
prometheus.GaugeValue,
|
||||
utils.MilliSecToSec(d.BaseUDPRTT),
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.CurrentTCPBandwidth,
|
||||
prometheus.GaugeValue,
|
||||
(d.CurrentTCPBandwidth*1000)/8,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.CurrentTCPRTT,
|
||||
prometheus.GaugeValue,
|
||||
utils.MilliSecToSec(d.CurrentTCPRTT),
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.CurrentUDPBandwidth,
|
||||
prometheus.GaugeValue,
|
||||
(d.CurrentUDPBandwidth*1000)/8,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.CurrentUDPRTT,
|
||||
prometheus.GaugeValue,
|
||||
utils.MilliSecToSec(d.CurrentUDPRTT),
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.TotalReceivedBytes,
|
||||
prometheus.CounterValue,
|
||||
d.TotalReceivedBytes,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.TotalSentBytes,
|
||||
prometheus.CounterValue,
|
||||
d.TotalSentBytes,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.UDPPacketsReceivedPersec,
|
||||
prometheus.CounterValue,
|
||||
d.UDPPacketsReceivedPersec,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.UDPPacketsSentPersec,
|
||||
prometheus.CounterValue,
|
||||
d.UDPPacketsSentPersec,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.FECRate,
|
||||
prometheus.GaugeValue,
|
||||
d.FECRate,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LossRate,
|
||||
prometheus.GaugeValue,
|
||||
d.LossRate,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.RetransmissionRate,
|
||||
prometheus.GaugeValue,
|
||||
d.RetransmissionRate,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
@@ -352,7 +352,7 @@ func (c *collector) collectRemoteFXGraphicsCounters(ctx *types.ScrapeContext, ch
|
||||
|
||||
for _, d := range dst {
|
||||
// only connect metrics for remote named sessions
|
||||
n := strings.ToLower(d.Name)
|
||||
n := strings.ToLower(normalizeSessionName(d.Name))
|
||||
if n == "" || n == "services" || n == "console" {
|
||||
continue
|
||||
}
|
||||
@@ -360,60 +360,65 @@ func (c *collector) collectRemoteFXGraphicsCounters(ctx *types.ScrapeContext, ch
|
||||
c.AverageEncodingTime,
|
||||
prometheus.GaugeValue,
|
||||
utils.MilliSecToSec(d.AverageEncodingTime),
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.FrameQuality,
|
||||
prometheus.GaugeValue,
|
||||
d.FrameQuality,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.FramesSkippedPerSecondInsufficientResources,
|
||||
prometheus.CounterValue,
|
||||
d.FramesSkippedPerSecondInsufficientClientResources,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
"client",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.FramesSkippedPerSecondInsufficientResources,
|
||||
prometheus.CounterValue,
|
||||
d.FramesSkippedPerSecondInsufficientNetworkResources,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
"network",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.FramesSkippedPerSecondInsufficientResources,
|
||||
prometheus.CounterValue,
|
||||
d.FramesSkippedPerSecondInsufficientServerResources,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
"server",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.GraphicsCompressionratio,
|
||||
prometheus.GaugeValue,
|
||||
d.GraphicsCompressionratio,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.InputFramesPerSecond,
|
||||
prometheus.CounterValue,
|
||||
d.InputFramesPerSecond,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.OutputFramesPerSecond,
|
||||
prometheus.CounterValue,
|
||||
d.OutputFramesPerSecond,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.SourceFramesPerSecond,
|
||||
prometheus.CounterValue,
|
||||
d.SourceFramesPerSecond,
|
||||
d.Name,
|
||||
normalizeSessionName(d.Name),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeSessionName ensure that the session is the same between WTS API and performance counters
|
||||
func normalizeSessionName(sessionName string) string {
|
||||
return strings.Replace(sessionName, "RDP-tcp", "RDP-Tcp", 1)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ package terminal_services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/headers/wtsapi32"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/perflib"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/types"
|
||||
"github.com/prometheus-community/windows_exporter/pkg/wmi"
|
||||
@@ -52,15 +55,16 @@ type collector struct {
|
||||
|
||||
connectionBrokerEnabled bool
|
||||
|
||||
hServer syscall.Handle
|
||||
|
||||
SessionInfo *prometheus.Desc
|
||||
LocalSessionCount *prometheus.Desc
|
||||
ConnectionBrokerPerformance *prometheus.Desc
|
||||
HandleCount *prometheus.Desc
|
||||
PageFaultsPersec *prometheus.Desc
|
||||
PageFileBytes *prometheus.Desc
|
||||
PageFileBytesPeak *prometheus.Desc
|
||||
PercentPrivilegedTime *prometheus.Desc
|
||||
PercentProcessorTime *prometheus.Desc
|
||||
PercentUserTime *prometheus.Desc
|
||||
PercentCPUTime *prometheus.Desc
|
||||
PoolNonpagedBytes *prometheus.Desc
|
||||
PoolPagedBytes *prometheus.Desc
|
||||
PrivateBytes *prometheus.Desc
|
||||
@@ -91,7 +95,6 @@ func (c *collector) SetLogger(logger log.Logger) {
|
||||
|
||||
func (c *collector) GetPerfCounter() ([]string, error) {
|
||||
return []string{
|
||||
"Terminal Services",
|
||||
"Terminal Services Session",
|
||||
"Remote Desktop Connection Broker Counterset",
|
||||
}, nil
|
||||
@@ -100,10 +103,10 @@ func (c *collector) GetPerfCounter() ([]string, error) {
|
||||
func (c *collector) Build() error {
|
||||
c.connectionBrokerEnabled = isConnectionBrokerServer(c.logger)
|
||||
|
||||
c.LocalSessionCount = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "local_session_count"),
|
||||
"Number of Terminal Services sessions",
|
||||
[]string{"session"},
|
||||
c.SessionInfo = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "session_info"),
|
||||
"Terminal Services sessions info",
|
||||
[]string{"session_name", "user", "host", "state"},
|
||||
nil,
|
||||
)
|
||||
c.ConnectionBrokerPerformance = prometheus.NewDesc(
|
||||
@@ -136,22 +139,10 @@ func (c *collector) Build() error {
|
||||
[]string{"session_name"},
|
||||
nil,
|
||||
)
|
||||
c.PercentPrivilegedTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "privileged_time_seconds_total"),
|
||||
"Total elapsed time that the threads of the process have spent executing code in privileged mode.",
|
||||
[]string{"session_name"},
|
||||
nil,
|
||||
)
|
||||
c.PercentProcessorTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "processor_time_seconds_total"),
|
||||
"Total elapsed time that all of the threads of this process used the processor to execute instructions.",
|
||||
[]string{"session_name"},
|
||||
nil,
|
||||
)
|
||||
c.PercentUserTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "user_time_seconds_total"),
|
||||
"Total elapsed time that this process's threads have spent executing code in user mode. Applications, environment Names, and integral Names execute in user mode.",
|
||||
[]string{"session_name"},
|
||||
c.PercentCPUTime = prometheus.NewDesc(
|
||||
prometheus.BuildFQName(types.Namespace, Name, "cpu_time_seconds_total"),
|
||||
"Total elapsed time that this process's threads have spent executing code.",
|
||||
[]string{"mode", "session_name"},
|
||||
nil,
|
||||
)
|
||||
c.PoolNonpagedBytes = prometheus.NewDesc(
|
||||
@@ -202,14 +193,22 @@ func (c *collector) Build() error {
|
||||
[]string{"session_name"},
|
||||
nil,
|
||||
)
|
||||
|
||||
var err error
|
||||
|
||||
c.hServer, err = wtsapi32.WTSOpenServer("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open WTS server: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Collect sends the metric values for each metric
|
||||
// to the provided prometheus Metric channel.
|
||||
func (c *collector) Collect(ctx *types.ScrapeContext, ch chan<- prometheus.Metric) error {
|
||||
if err := c.collectTSSessionCount(ctx, ch); err != nil {
|
||||
_ = level.Error(c.logger).Log("msg", "failed collecting terminal services session count metrics", "err", err)
|
||||
if err := c.collectWTSSessions(ch); err != nil {
|
||||
_ = level.Error(c.logger).Log("msg", "failed collecting terminal services session infos", "err", err)
|
||||
return err
|
||||
}
|
||||
if err := c.collectTSSessionCounters(ctx, ch); err != nil {
|
||||
@@ -227,46 +226,6 @@ func (c *collector) Collect(ctx *types.ScrapeContext, ch chan<- prometheus.Metri
|
||||
return nil
|
||||
}
|
||||
|
||||
type perflibTerminalServices struct {
|
||||
ActiveSessions float64 `perflib:"Active Sessions"`
|
||||
InactiveSessions float64 `perflib:"Inactive Sessions"`
|
||||
TotalSessions float64 `perflib:"Total Sessions"`
|
||||
}
|
||||
|
||||
func (c *collector) collectTSSessionCount(ctx *types.ScrapeContext, ch chan<- prometheus.Metric) error {
|
||||
dst := make([]perflibTerminalServices, 0)
|
||||
err := perflib.UnmarshalObject(ctx.PerfObjects["Terminal Services"], &dst, c.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(dst) == 0 {
|
||||
return errors.New("WMI query returned empty result set")
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LocalSessionCount,
|
||||
prometheus.GaugeValue,
|
||||
dst[0].ActiveSessions,
|
||||
"active",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LocalSessionCount,
|
||||
prometheus.GaugeValue,
|
||||
dst[0].InactiveSessions,
|
||||
"inactive",
|
||||
)
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.LocalSessionCount,
|
||||
prometheus.GaugeValue,
|
||||
dst[0].TotalSessions,
|
||||
"total",
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type perflibTerminalServicesSession struct {
|
||||
Name string
|
||||
HandleCount float64 `perflib:"Handle Count"`
|
||||
@@ -331,22 +290,25 @@ func (c *collector) collectTSSessionCounters(ctx *types.ScrapeContext, ch chan<-
|
||||
d.Name,
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.PercentPrivilegedTime,
|
||||
c.PercentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentPrivilegedTime,
|
||||
d.Name,
|
||||
"privileged",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.PercentProcessorTime,
|
||||
c.PercentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentProcessorTime,
|
||||
d.Name,
|
||||
"processor",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.PercentUserTime,
|
||||
c.PercentCPUTime,
|
||||
prometheus.CounterValue,
|
||||
d.PercentUserTime,
|
||||
d.Name,
|
||||
"user",
|
||||
)
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.PoolNonpagedBytes,
|
||||
@@ -439,3 +401,36 @@ func (c *collector) collectCollectionBrokerPerformanceCounter(ctx *types.ScrapeC
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collector) collectWTSSessions(ch chan<- prometheus.Metric) error {
|
||||
sessions, err := wtsapi32.WTSEnumerateSessionsEx(c.hServer, c.logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to enumerate WTS sessions: %w", err)
|
||||
}
|
||||
|
||||
for _, session := range sessions {
|
||||
userName := session.UserName
|
||||
if session.DomainName != "" {
|
||||
userName = fmt.Sprintf("%s\\%s", session.DomainName, session.UserName)
|
||||
}
|
||||
|
||||
for stateID, stateName := range wtsapi32.WTSSessionStates {
|
||||
isState := 0.0
|
||||
if session.State == stateID {
|
||||
isState = 1.0
|
||||
}
|
||||
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
c.SessionInfo,
|
||||
prometheus.GaugeValue,
|
||||
isState,
|
||||
strings.Replace(session.SessionName, "#", " ", -1),
|
||||
userName,
|
||||
session.HostName,
|
||||
stateName,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,5 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func BenchmarkCollector(b *testing.B) {
|
||||
|
||||
testutils.FuncBenchmarkCollector(b, terminal_services.Name, terminal_services.NewWithFlags)
|
||||
|
||||
}
|
||||
|
||||
198
pkg/headers/wtsapi32/wtsapi32.go
Normal file
198
pkg/headers/wtsapi32/wtsapi32.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package wtsapi32
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/go-kit/log"
|
||||
"github.com/go-kit/log/level"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type WTSTypeClass int
|
||||
|
||||
// The valid values for the WTSTypeClass enumeration
|
||||
const (
|
||||
WTSTypeProcessInfoLevel0 WTSTypeClass = iota
|
||||
WTSTypeProcessInfoLevel1
|
||||
WTSTypeSessionInfoLevel1
|
||||
)
|
||||
|
||||
type WTSConnectState uint32
|
||||
|
||||
const (
|
||||
// wtsActive A user is logged on to the WinStation. This state occurs when a user is signed in and actively connected to the device.
|
||||
wtsActive WTSConnectState = iota
|
||||
// wtsConnected The WinStation is connected to the client.
|
||||
wtsConnected
|
||||
// wtsConnectQuery The WinStation is in the process of connecting to the client.
|
||||
wtsConnectQuery
|
||||
// wtsShadow The WinStation is shadowing another WinStation.
|
||||
wtsShadow
|
||||
// wtsDisconnected The WinStation is active but the client is disconnected.
|
||||
// This state occurs when a user is signed in but not actively connected to the device, such as when the user has chosen to exit to the lock screen.
|
||||
wtsDisconnected
|
||||
// wtsIdle The WinStation is waiting for a client to connect.
|
||||
wtsIdle
|
||||
// wtsListen The WinStation is listening for a connection. A listener session waits for requests for new client connections.
|
||||
// No user is logged on a listener session. A listener session cannot be reset, shadowed, or changed to a regular client session.
|
||||
wtsListen
|
||||
// wtsReset The WinStation is being reset.
|
||||
wtsReset
|
||||
// wtsDown The WinStation is down due to an error.
|
||||
wtsDown
|
||||
// wtsInit The WinStation is initializing.
|
||||
wtsInit
|
||||
)
|
||||
|
||||
// WTSSessionInfo1w contains information about a session on a Remote Desktop Session Host (RD Session Host) server.
|
||||
// docs: https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w
|
||||
type wtsSessionInfo1 struct {
|
||||
// ExecEnvID An identifier that uniquely identifies the session within the list of sessions returned by the WTSEnumerateSessionsEx function.
|
||||
ExecEnvID uint32
|
||||
// State A value of the WTSConnectState enumeration type that specifies the connection state of a Remote Desktop Services session.
|
||||
State uint32
|
||||
// SessionID A session identifier assigned by the RD Session Host server, RD Virtualization Host server, or virtual machine.
|
||||
SessionID uint32
|
||||
// pSessionName A pointer to a null-terminated string that contains the name of this session. For example, "services", "console", or "RDP-Tcp#0".
|
||||
pSessionName *uint16
|
||||
// pHostName A pointer to a null-terminated string that contains the name of the computer that the session is running on.
|
||||
// If the session is running directly on an RD Session Host server or RD Virtualization Host server, the string contains NULL.
|
||||
// If the session is running on a virtual machine, the string contains the name of the virtual machine.
|
||||
pHostName *uint16
|
||||
// pUserName A pointer to a null-terminated string that contains the name of the user who is logged on to the session.
|
||||
// If no user is logged on to the session, the string contains NULL.
|
||||
pUserName *uint16
|
||||
// pDomainName A pointer to a null-terminated string that contains the domain name of the user who is logged on to the session.
|
||||
// If no user is logged on to the session, the string contains NULL.
|
||||
pDomainName *uint16
|
||||
// pFarmName A pointer to a null-terminated string that contains the name of the farm that the virtual machine is joined to.
|
||||
// If the session is not running on a virtual machine that is joined to a farm, the string contains NULL.
|
||||
pFarmName *uint16
|
||||
}
|
||||
|
||||
type WTSSession struct {
|
||||
ExecEnvID uint32
|
||||
State WTSConnectState
|
||||
SessionID uint32
|
||||
SessionName string
|
||||
HostName string
|
||||
UserName string
|
||||
DomainName string
|
||||
FarmName string
|
||||
}
|
||||
|
||||
var (
|
||||
wtsapi32 = windows.NewLazySystemDLL("wtsapi32.dll")
|
||||
|
||||
procWTSOpenServerEx = wtsapi32.NewProc("WTSOpenServerExW")
|
||||
procWTSEnumerateSessionsEx = wtsapi32.NewProc("WTSEnumerateSessionsExW")
|
||||
procWTSFreeMemoryEx = wtsapi32.NewProc("WTSFreeMemoryExW")
|
||||
procWTSCloseServer = wtsapi32.NewProc("WTSCloseServer")
|
||||
|
||||
WTSSessionStates = map[WTSConnectState]string{
|
||||
wtsActive: "active",
|
||||
wtsConnected: "connected",
|
||||
wtsConnectQuery: "connect_query",
|
||||
wtsShadow: "shadow",
|
||||
wtsDisconnected: "disconnected",
|
||||
wtsIdle: "idle",
|
||||
wtsListen: "listen",
|
||||
wtsReset: "reset",
|
||||
wtsDown: "down",
|
||||
wtsInit: "init",
|
||||
}
|
||||
)
|
||||
|
||||
func WTSOpenServer(server string) (syscall.Handle, error) {
|
||||
var (
|
||||
err error
|
||||
serverName *uint16
|
||||
)
|
||||
|
||||
if server != "" {
|
||||
serverName, err = syscall.UTF16PtrFromString(server)
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
}
|
||||
|
||||
r1, _, err := procWTSOpenServerEx.Call(uintptr(unsafe.Pointer(serverName)))
|
||||
serverHandle := syscall.Handle(r1)
|
||||
|
||||
if serverHandle == syscall.InvalidHandle {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
|
||||
return serverHandle, nil
|
||||
}
|
||||
|
||||
func WTSCloseServer(server syscall.Handle) error {
|
||||
_, _, err := procWTSCloseServer.Call(uintptr(server))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to close server: %w", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func WTSFreeMemoryEx(class WTSTypeClass, pMemory uintptr, NumberOfEntries uint32) error {
|
||||
_, _, err := procWTSFreeMemoryEx.Call(
|
||||
uintptr(class),
|
||||
pMemory,
|
||||
uintptr(NumberOfEntries),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func WTSEnumerateSessionsEx(server syscall.Handle, logger log.Logger) ([]WTSSession, error) {
|
||||
var sessionInfoPointer uintptr
|
||||
var count uint32
|
||||
|
||||
pLevel := uint32(1)
|
||||
r1, _, err := procWTSEnumerateSessionsEx.Call(
|
||||
uintptr(server),
|
||||
uintptr(unsafe.Pointer(&pLevel)),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(&sessionInfoPointer)),
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
)
|
||||
|
||||
if r1 != 1 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sessionInfoPointer != 0 {
|
||||
defer func(class WTSTypeClass, pMemory uintptr, NumberOfEntries uint32) {
|
||||
err := WTSFreeMemoryEx(class, pMemory, NumberOfEntries)
|
||||
if err != nil {
|
||||
_ = level.Error(logger).Log("msg", "failed to free memory", "err", err)
|
||||
}
|
||||
}(WTSTypeSessionInfoLevel1, sessionInfoPointer, count)
|
||||
}
|
||||
|
||||
var sizeTest wtsSessionInfo1
|
||||
sessionSize := unsafe.Sizeof(sizeTest)
|
||||
|
||||
sessions := make([]WTSSession, 0, count)
|
||||
for i := uint32(0); i < count; i++ {
|
||||
curPtr := unsafe.Pointer(sessionInfoPointer + (uintptr(i) * sessionSize))
|
||||
data := (*wtsSessionInfo1)(curPtr)
|
||||
|
||||
sessionInfo := WTSSession{
|
||||
ExecEnvID: data.ExecEnvID,
|
||||
State: WTSConnectState(data.State),
|
||||
SessionID: data.SessionID,
|
||||
SessionName: windows.UTF16PtrToString(data.pSessionName),
|
||||
HostName: windows.UTF16PtrToString(data.pHostName),
|
||||
UserName: windows.UTF16PtrToString(data.pUserName),
|
||||
DomainName: windows.UTF16PtrToString(data.pDomainName),
|
||||
FarmName: windows.UTF16PtrToString(data.pFarmName),
|
||||
}
|
||||
sessions = append(sessions, sessionInfo)
|
||||
}
|
||||
|
||||
return sessions, nil
|
||||
}
|
||||
Reference in New Issue
Block a user