Regex to match first space only after every word

Regex to match first space only after every word. I want to replace the first space after every word with a comma so I can pull this into a custom object.

SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE 
 services                                    0  Disc                        
 console                                     3  Conn                        
 rdp-tcp                                 65536  Listen   

Something like this should do:

Edit: Moved code over to a Gist, so the angle brackets wouldn’t make our forum software confused.

damn here is my problem

I was using ‘\b\s’,‘,’ which worked but the user name column is sometimes blank so I have to add an extra , to shift the columns over when that field is blank

here is my full command

$sessions = (QWINSTA /SERVER:$($server.Name) | foreach {(($_.trim() -replace “\b\s”,“,”)) + “,”} | ConvertFrom-Csv)

Absolutely nothing regex can do to help you with that. What you could do is parse the header row to figure out roughly where data should be, then start at that column in each data row to figure out where to put the comma.

Or even better, find a way to work with objects here instead of table-formatted text. Table-formatted is fantastic for humans to read, and absolute crap for computer parsing. (Maybe search for C# example code of how to enumerate TS sessions; that’ll be the easiest thing to port over to PowerShell.)

since session are always numbers and user id’s are never numbers this works but I donlt like doing this… if anyone knows of a better way please chime in

$sessions = (QWINSTA  /SERVER:$($server.Name) | foreach {(($_.trim() -replace "\b\s",",")) + ","} | ConvertFrom-Csv) 
Foreach($s in $Sessions){
    if(!($s.USERNAME -like '^a-z')){
        $newsession += New-Object PSObject -Property([Ordered]@{SESSIONNAME = $s.SESSIONNAME; USERNAME = ""; Id = $s.USERNAME; STATE = $s.ID})
    }
}

I did a search on this and took some code from StackOverflow. The C# / Interop stuff isn’t terribly easy to read (and it assumes you’re running on a 64-bit OS, which is a little bit sloppy but probably safe these days), but it works. :slight_smile:

Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TerminalTools
{
    public class TermServicesManager
    {

        [DllImport("wtsapi32.dll")]
        static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

        [DllImport("wtsapi32.dll")]
        static extern void WTSCloseServer(IntPtr hServer);

        [DllImport("Wtsapi32.dll")]
        public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass,
            out System.IntPtr ppBuffer, out uint pBytesReturned);

        [DllImport("wtsapi32.dll")]
        static extern Int32 WTSEnumerateSessions(IntPtr hServer, [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
            [MarshalAs(UnmanagedType.U4)] Int32 Version, ref IntPtr ppSessionInfo, [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);

        [DllImport("wtsapi32.dll")]
        static extern void WTSFreeMemory(IntPtr pMemory);

        [StructLayout(LayoutKind.Sequential)]
        private struct WTS_SESSION_INFO
        {
            public Int32 SessionID;
            [MarshalAs(UnmanagedType.LPStr)]
            public String pWinStationName;
            public WTS_CONNECTSTATE_CLASS State;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct WTS_CLIENT_ADDRESS
        {
            public uint AddressFamily;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public byte[] Address;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct WTS_CLIENT_DISPLAY
        {
            public uint HorizontalResolution;
            public uint VerticalResolution;
            public uint ColorDepth;
        }

        public enum WTS_CONNECTSTATE_CLASS
        {
            Active,
            Connected,
            ConnectQuery,
            Shadow,
            Disconnected,
            Idle,
            Listen,
            Reset,
            Down,
            Init
        }

        public enum WTS_INFO_CLASS
        {
            InitialProgram = 0,
            ApplicationName = 1,
            WorkingDirectory = 2,
            OEMId = 3,
            SessionId = 4,
            UserName = 5,
            WinStationName = 6,
            DomainName = 7,
            ConnectState = 8,
            ClientBuildNumber = 9,
            ClientName = 10,
            ClientDirectory = 11,
            ClientProductId = 12,
            ClientHardwareId = 13,
            ClientAddress = 14,
            ClientDisplay = 15,
            ClientProtocolType = 16
        }

                        private static IntPtr OpenServer(string Name)
    {
        IntPtr server = WTSOpenServer(Name);
        return server;
    }

                    private static void CloseServer(IntPtr ServerHandle)
    {
        WTSCloseServer(ServerHandle);
    }

        public static List ListSessions(string ServerName)
        {
            IntPtr server = IntPtr.Zero;
            List ret = new List();
            server = OpenServer(ServerName);

            try
            {
                IntPtr ppSessionInfo = IntPtr.Zero;

                Int32 count = 0;
                Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
                Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));

                Int64 current = (Int64)ppSessionInfo;

                if (retval != 0)
                {
                    for (int i = 0; i < count; i++)
                    {
                        WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
                        current += dataSize;

                        ret.Add(new TerminalSessionData(si.SessionID, si.State, si.pWinStationName));
                    }

                    WTSFreeMemory(ppSessionInfo);
                }
            }
            finally
            {
                CloseServer(server);
            }

            return ret;
        }

        public static TerminalSessionInfo GetSessionInfo(string ServerName, int SessionId)
        {
            IntPtr server = IntPtr.Zero;
            server = OpenServer(ServerName);
            System.IntPtr buffer = IntPtr.Zero;
            uint bytesReturned;
            TerminalSessionInfo data = new TerminalSessionInfo();

            try
            {
                bool worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ApplicationName, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                string strData = Marshal.PtrToStringAnsi(buffer);
                data.ApplicationName = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientAddress, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                WTS_CLIENT_ADDRESS si = (WTS_CLIENT_ADDRESS)Marshal.PtrToStructure((System.IntPtr)buffer, typeof(WTS_CLIENT_ADDRESS));
                data.ClientAddress = si;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientBuildNumber, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                int lData = Marshal.ReadInt32(buffer);
                data.ClientBuildNumber = lData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientDirectory, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                strData = Marshal.PtrToStringAnsi(buffer);
                data.ClientDirectory = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientDisplay, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                WTS_CLIENT_DISPLAY cd = (WTS_CLIENT_DISPLAY)Marshal.PtrToStructure((System.IntPtr)buffer, typeof(WTS_CLIENT_DISPLAY));
                data.ClientDisplay = cd;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientHardwareId, out buffer, out bytesReturned);

                if (!worked)
                    return data;

                lData = Marshal.ReadInt32(buffer);
                data.ClientHardwareId = lData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientName, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.ClientName = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientProductId, out buffer, out bytesReturned);
                Int16 intData = Marshal.ReadInt16(buffer);
                data.ClientProductId = intData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ClientProtocolType, out buffer, out bytesReturned);
                intData = Marshal.ReadInt16(buffer);
                data.ClientProtocolType = intData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.ConnectState, out buffer, out bytesReturned);
                lData = Marshal.ReadInt32(buffer);
                data.ConnectState = (WTS_CONNECTSTATE_CLASS)Enum.ToObject(typeof(WTS_CONNECTSTATE_CLASS), lData);

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.DomainName, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.DomainName = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.InitialProgram, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.InitialProgram = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.OEMId, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.OEMId = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.SessionId, out buffer, out bytesReturned);
                lData = Marshal.ReadInt32(buffer);
                data.SessionId = lData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.UserName, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.UserName = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.WinStationName, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.WinStationName = strData;

                worked = WTSQuerySessionInformation(server, SessionId,
                    WTS_INFO_CLASS.WorkingDirectory, out buffer, out bytesReturned);
                strData = Marshal.PtrToStringAnsi(buffer);
                data.WorkingDirectory = strData;
            }
            finally
            {
                WTSFreeMemory(buffer);
                buffer = IntPtr.Zero;
                CloseServer(server);
            }

            return data;
        }

    }

    public class TerminalSessionData
    {
        public int SessionId;
        public TermServicesManager.WTS_CONNECTSTATE_CLASS ConnectionState;
        public string StationName;

                            public TerminalSessionData(int sessionId, TermServicesManager.WTS_CONNECTSTATE_CLASS connState, string stationName)
    {
        SessionId = sessionId;
        ConnectionState = connState;
        StationName = stationName;
    }

                    public override string ToString()
    {
        return String.Format("{0} {1} {2}", SessionId, ConnectionState, StationName);
    }
    }

    public class TerminalSessionInfo
    {
        public string InitialProgram;
        public string ApplicationName;
        public string WorkingDirectory;
        public string OEMId;
        public int SessionId;
        public string UserName;
        public string WinStationName;
        public string DomainName;
        public TermServicesManager.WTS_CONNECTSTATE_CLASS ConnectState;
        public int ClientBuildNumber;
        public string ClientName;
        public string ClientDirectory;
        public int ClientProductId;
        public int ClientHardwareId;
        public TermServicesManager.WTS_CLIENT_ADDRESS ClientAddress;
        public TermServicesManager.WTS_CLIENT_DISPLAY ClientDisplay;
        public int ClientProtocolType;
    }
}
'@

To call the two (ListSessions and GetSessionInfo) that are giving us the info, do something like this:

[TerminalTools.TermServicesManager]::ListSessions($env:COMPUTERNAME) |
ForEach-Object { [TerminalTools.TermServicesManager]::GetSessionInfo($env:COMPUTERNAME, $_.SessionId) }

damn lol

Just curious …
If this is specifically for Terminal Services, have you looked at the module out on Codeplex? It will save you a lot of time.
http://psterminalservices.codeplex.com/

I played with this just for fun. Since this is fixed width output, I figured I would just use the column header widths to determine column width and use that for the data widths.

$data = qwinsta
$result = $data[0] -match "(?'Column1'.+?\s+)(?'Column2'.+?\s+)(?'Column3'.+?\s+)(?'Column4'.+?\s+)(?'Column5'.+?\s+)(?'Column6'.+)"
$headers = $Matches
$data | Select-Object -Skip 1 | ForEach-Object {
    $result = $_ -match "(?'Column1'.{$(($headers['Column1']).Length)})(?'Column2'.{$(($headers['Column2']).Length)})(?'Column3'.{$(($headers['Column3']).Length)})(?'Column4'.{$(($headers['Column4']).Length)})(?'Column5'.{$(($headers['Column5']).Length)})(?'Column6'.{$(($headers['Column6']).Length)})"
    [PSCustomObject]@{
        ($headers['Column1']).trim() = ($matches['Column1']).substring(1).trim()
        ($headers['Column2']).trim() = ($matches['Column2']).trim()
        ($headers['Column3']).trim() = ($matches['Column3']).trim()
        ($headers['Column4']).trim() = ($matches['Column4']).trim()
        ($headers['Column5']).trim() = ($matches['Column5']).trim()
        ($headers['Column6']).trim() = ($matches['Column6']).trim()
    }
}

This first appeared to work well, but then I noticed that the ID column appears to be right aligned. This makes the column header width not necessarily match the data.

Results:

SESSIONNAME USERNAME ID STATE  TYPE DEVICE
----------- -------- -- -----  ---- ------
services             0  Disc              
console              3  Conn              
rdp-tcp     655      36 Listen            

Further testing of qwinsta; however, appears to prove that the output is always the same fixed width, not adjusted fixed width based on values. If that continues to be the case, using regex to match the fixed with values is simple enough.

qwinsta | Select-Object -Skip 1 | ForEach-Object {
    $result = $_ -match ".(?'sessionname'.{16})\s\s(?'username'.{20})\s\s(?'id'.{5})\s\s(?'state'.{6})\s\s(?'type'.{10})\s\s(?'device'.{7})"
    [PSCustomObject]@{
        SESSIONNAME = ($matches['sessionname']).trim()
        USERNAME = ($matches['username']).trim()
        ID = ($matches['id']).trim()
        STATE = ($matches['state']).trim()
        TYPE = ($matches['type']).trim()
        DEVICE = ($matches['device']).trim()
    }
}

Results

SESSIONNAME USERNAME ID    STATE  TYPE DEVICE
----------- -------- --    -----  ---- ------
services             0     Disc              
console              3     Conn              
rdp-tcp              65536 Listen            

This can also be done using substring if the width is fixed.

qwinsta | Select-Object -Skip 1 | ForEach-Object {
    [PSCustomObject]@{
        SESSIONNAME = $_.substring(1,17).trim()
        USERNAME = $_.substring(19,21).trim()
        ID = $_.substring(41,5).trim()
        STATE = $_.substring(48,7).trim()
        TYPE = $_.substring(56,11).trim()
        DEVICE = $_.substring(68).trim()
    }
}

Results:

SESSIONNAME USERNAME ID    STATE  TYPE DEVICE
----------- -------- --    -----  ---- ------
services             0     Disc              
console              3     Conn              
rdp-tcp              65536 Listen            

For purposes of speed, the substring method runs slightly faster on average with a 10,001 sample measurement.
RegEx method (1.39082208779123 milliseconds on average)
Substring method (1.24752035796421 milliseconds on average)

Please advise if we can get the output of below script over the email

Query Active Directory for computers running a Server operating system

$Servers = "Get-ADComputer -Filter {OperatingSystem -like “server”}

Loop through the list to query each server for login sessions

ForEach ($Server in $Servers) {
$ServerName = $Server.Name

# When running interactively, uncomment the Write-Host line below to show which server is being queried 
# Write-Host "Querying $ServerName" 

# Run the qwinsta.exe and parse the output 
$ServerName
$AllSessions = @()

qwinsta /server:$ServerName | ForEach {
If($.SubString(1,18).Trim() -ne “SESSIONNAME”){
$AllSessions += [pscustomobject]@{ SessionName = $
.SubString(1,18).Trim();
UserName = $.SubString(19,22).Trim();
ID = $
.SubString(41,5).Trim();
State = $.SubString(48,8).Trim();
Type = $
.SubString(56,12).Trim();
Device = $_.SubString(68).Trim()}
}
}

Please advise if we can get the output of below script over the email.