I'm trying to hook the winsock connect function and route the TCP connection through socks5 proxy /w auth.
This works if the socket is a blocking socket, but while using firefox ( nonblocking sockets ) I get a lot of 10035, 10022 winsock Errors.
How can i determine if it's a nonblocking / blocking socket?
I would really appreciate any hints or ideas to achieve the functionality to hook the wsock connect function and route tcp traffic through a socks5 server.
I can put the demo application on github if anybody wants to test it. ( Works with any version of firefox )
Edit1: https://github.com/duketwo/WinsockConnectHookSocks5/
( You have to edit the proxy information in WSockConnectHook/HookManager.cs and the path of firefox in Injector/MainForm.cs )
Edit2: It's easyhook which is causing the trouble, anything after the original function call doesn't work properly.
Edit3: Seems like i got it working with many flaws, in fact it is required differentiate between nonblocking sockets and blocking sockets. Any ideas how to achieve this?
Edit4: Windows doesn't offer any method to retrieve the blocking-attribute of a socket, so I might have to hook the ioctlsocket function to keep track of the blocking status of the sockets.
Thanks
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using EasyHook;
using System.IO;
using System.Windows.Forms;
namespace WSockConnectHook
{
public class WinSockConnectController : IDisposable, IHook
{
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
private delegate int WinsockConnectDelegate(IntPtr s, IntPtr addr, int addrsize);
[DllImport("WS2_32.dll", SetLastError = true)]
public static extern int connect(IntPtr s, IntPtr addr, int addrsize);
[StructLayout(LayoutKind.Sequential, Size = 16)]
public struct sockaddr_in
{
public const int Size = 16;
public short sin_family;
public ushort sin_port;
public struct in_addr
{
public uint S_addr;
public struct _S_un_b
{
public byte s_b1, s_b2, s_b3, s_b4;
}
public _S_un_b S_un_b;
public struct _S_un_w
{
public ushort s_w1, s_w2;
}
public _S_un_w S_un_w;
}
public in_addr sin_addr;
}
[DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int WSAGetLastError();
[DllImport("ws2_32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern void WSASetLastError(int set);
[DllImport("Ws2_32.dll", CharSet = CharSet.Ansi)]
public static extern uint inet_addr(string cp);
[DllImport("Ws2_32.dll")]
public static extern ushort htons(ushort hostshort);
[DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr socket(short af, short socket_type, int protocol);
[DllImport("Ws2_32.dll")]
public static extern int send(IntPtr s, IntPtr buf, int len, int flags);
[DllImport("Ws2_32.dll")]
public static extern int recv(IntPtr s, IntPtr buf, int len, int flags);
[DllImport("ws2_32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int closesocket(IntPtr s);
[DllImport("Ws2_32.dll")]
public static extern ushort ntohs(ushort netshort);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void SetLastError(int errorCode);
private string _name;
private LocalHook _hook;
public bool Error { get; set; }
public string Name { get; set; }
private string proxyIp, proxyPort, proxyUser, proxyPass;
public WinSockConnectController(IntPtr address, string proxyIp, string proxyPort, string proxyUser, string proxyPass)
{
this.Name = typeof(WinSockConnectController).Name;
this.proxyIp = proxyIp;
this.proxyPort = proxyPort;
this.proxyUser = proxyUser;
this.proxyPass = proxyPass;
try
{
_name = string.Format("WinsockHook_{0:X}", address.ToInt32());
_hook = LocalHook.Create(address, new WinsockConnectDelegate(WinsockConnectDetour), this);
_hook.ThreadACL.SetExclusiveACL(new Int32[] { 1 });
}
catch (Exception)
{
this.Error = true;
}
}
private object wSockLock = new object();
private int WinsockConnectDetour(IntPtr s, IntPtr addr, int addrsize)
{
lock (wSockLock)
{
// retrieve remote ip
sockaddr_in structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
string remoteIp = new System.Net.IPAddress(structure.sin_addr.S_addr).ToString();
ushort remotePort = ntohs(structure.sin_port);
HookManager.Log("Ip: " + remoteIp + " Port: " + remotePort.ToString() + " Addrsize: " + addrsize);
if (!proxyIp.Equals(""))
//if (!proxyIp.Equals(""))
{
// connect to socks5 server
SetAddr(s, addr, proxyIp, proxyPort);
var result = Connect(s, addr, addrsize);
if (result == -1)
return -1;
// send socks 5 request
IntPtr socksProtocolRequest = SetUpSocks5Request();
result = send(s, socksProtocolRequest, 4, 0);
if (result == -1)
return -1;
// retrieve server repsonse
var response = Recieve(s, 2);
if (response == IntPtr.Zero)
return -1;
byte[] recvBytes = new byte[2] { Marshal.ReadByte(response), Marshal.ReadByte(response, 1) };
if (recvBytes[1] == 255)
{
HookManager.Log("No authentication method was accepted by the proxy server");
return -1;
}
if (recvBytes[0] != 5)
{
HookManager.Log("No SOCKS5 proxy");
return -1;
}
// if auth request response, send authenicate request
if (recvBytes[1] == 2)
{
int length = 0;
var authenticateRequest = SetUpAuthenticateRequest(proxyUser, proxyPass, out length);
result = Send(s, authenticateRequest, length);
response = Recieve(s, 2);
if (response == IntPtr.Zero)
return -1;
recvBytes = new byte[2] { Marshal.ReadByte(response), Marshal.ReadByte(response, 1) };
if (recvBytes[1] != 0)
{
HookManager.Log("Proxy: incorrect username/password");
return -1;
}
}
// request bind with server
var bindRequest = SetUpBindWithRemoteHost(remoteIp, remotePort);
result = Send(s, bindRequest, 10);
if (result == -1)
return -1;
// response
response = Recieve(s, 10);
if (response == IntPtr.Zero)
return -1;
if (!VerifyBindResponse(response))
return -1;
// success
WSASetLastError(0);
SetLastError(0);
// clean memory
foreach (var ptr in allocatedMemory)
Marshal.FreeHGlobal(ptr);
allocatedMemory.Clear();
return 0;
}
else
{
var result = connect(s, addr, addrsize);
return result;
}
}
}
private int Connect(IntPtr socket, IntPtr addr, int addrsize)
{
var result = connect(socket, addr, addrsize);
while (result == -1)
{
var errorcode = WSAGetLastError();
HookManager.Log("Error: " + errorcode);
if (errorcode == 10056)
break;
if (errorcode == 10037)
break;
if (errorcode != 10035 && errorcode != 10037)
return -1;
//flag = 1;
result = connect(socket, addr, addrsize);
}
return result;
}
private int Send(IntPtr socket, IntPtr buf, int len)
{
var result = send(socket, buf, len, 0);
while (result == -1)
{
var errorcode = WSAGetLastError();
HookManager.Log("Error: " + errorcode);
if (errorcode == 10056)
break;
if (errorcode == 10037)
break;
if (errorcode != 10035 && errorcode != 10037)
return -1;
result = send(socket, buf, 4, 0);
}
return result;
}
private List<IntPtr> allocatedMemory = new List<IntPtr>();
private IntPtr Recieve(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error2: " + WSAGetLastError());
return IntPtr.Zero;
}
return buffer;
}
private IntPtr RecieveAuth(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error3: " + WSAGetLastError());
return IntPtr.Zero; ;
}
if (result == 0)
return buffer;
if (result != 2)
{
HookManager.Log("Proxy: Bad response from server");
return IntPtr.Zero;
}
return buffer;
}
private IntPtr RecieveBind(IntPtr socket, int len)
{
var buffer = Marshal.AllocHGlobal(len);
allocatedMemory.Add(buffer);
var result = recv(socket, buffer, len, 0);
if (result == -1)
{
HookManager.Log("Error3: " + WSAGetLastError());
return IntPtr.Zero; ;
}
if (result == 0)
return buffer;
if (result != 10)
{
HookManager.Log("Proxy: Bad response from server");
return IntPtr.Zero;
}
return buffer;
}
private void SetAddr(IntPtr socket, IntPtr addr, string ip, string port)
{
sockaddr_in structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
string originalip = new System.Net.IPAddress(structure.sin_addr.S_addr).ToString();
ushort originalport = ntohs(structure.sin_port);
structure.sin_addr.S_addr = inet_addr(ip);
structure.sin_port = htons(Convert.ToUInt16(port));
Marshal.StructureToPtr(structure, addr, true);
structure = (sockaddr_in)Marshal.PtrToStructure(addr, typeof(sockaddr_in));
}
private IntPtr SetUpSocks5Request()
{
var initialRequest = Marshal.AllocHGlobal(4);
Marshal.WriteByte(initialRequest, Convert.ToByte(5));
Marshal.WriteByte(initialRequest + 1, Convert.ToByte(2));
Marshal.WriteByte(initialRequest + 2, Convert.ToByte(0));
Marshal.WriteByte(initialRequest + 3, Convert.ToByte(2));
return initialRequest;
}
private IntPtr SetUpAuthenticateRequest(string username, string password, out int index)
{
index = 0;
var size = 3 + Encoding.Default.GetBytes(username).Length + Encoding.Default.GetBytes(password).Length;
var authenticateBuffer = Marshal.AllocHGlobal(size);
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(1));
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(username.Length));
byte[] rawBytes;
if (username.Length > 0)
{
rawBytes = Encoding.Default.GetBytes(username);
for (int i = 0; i < rawBytes.Length; i++)
{
Marshal.WriteByte(authenticateBuffer + index++, rawBytes[i]);
}
}
Marshal.WriteByte(authenticateBuffer + index++, Convert.ToByte(password.Length));
if (password.Length > 0)
{
rawBytes = Encoding.Default.GetBytes(password);
for (int i = 0; i < rawBytes.Length; i++)
{
Marshal.WriteByte(authenticateBuffer + index++, rawBytes[i]);
}
}
return authenticateBuffer;
}
private IntPtr SetUpBindWithRemoteHost(string eveIP, ushort evePort)
{
var bindWithEveBuffer = Marshal.AllocHGlobal(10);
var iplist = eveIP.Split('.').ToList();
byte[] portbyte = BitConverter.GetBytes(evePort).Reverse().ToArray();
byte[] newbyte = new byte[2];
int indexy = 0;
foreach (var byty in portbyte)
{
newbyte[indexy] = byty;
indexy++;
}
// bind with remote server
Marshal.WriteByte(bindWithEveBuffer, Convert.ToByte(5));
Marshal.WriteByte(bindWithEveBuffer + 1, Convert.ToByte(1));
Marshal.WriteByte(bindWithEveBuffer + 2, Convert.ToByte(0));
Marshal.WriteByte(bindWithEveBuffer + 3, Convert.ToByte(1));
Marshal.WriteByte(bindWithEveBuffer + 4, Convert.ToByte(iplist[0]));
Marshal.WriteByte(bindWithEveBuffer + 5, Convert.ToByte(iplist[1]));
Marshal.WriteByte(bindWithEveBuffer + 6, Convert.ToByte(iplist[2]));
Marshal.WriteByte(bindWithEveBuffer + 7, Convert.ToByte(iplist[3]));
Marshal.WriteByte(bindWithEveBuffer + 8, newbyte[0]);
Marshal.WriteByte(bindWithEveBuffer + 9, newbyte[1]);
return bindWithEveBuffer;
}
private bool VerifyBindResponse(IntPtr buffer)
{
var recvBytes = new byte[10] { Marshal.ReadByte(buffer), Marshal.ReadByte(buffer, 1), Marshal.ReadByte(buffer, 2), Marshal.ReadByte(buffer, 3), Marshal.ReadByte(buffer, 4), Marshal.ReadByte(buffer, 5), Marshal.ReadByte(buffer, 6), Marshal.ReadByte(buffer, 7), Marshal.ReadByte(buffer, 8), Marshal.ReadByte(buffer, 9) };
if (recvBytes[1] != 0)
{
if (recvBytes[1] == 1)
HookManager.Log("General failure");
if (recvBytes[1] == 2)
HookManager.Log("connection not allowed by ruleset");
if (recvBytes[1] == 3)
HookManager.Log("network unreachable");
if (recvBytes[1] == 4)
HookManager.Log("host unreachable");
if (recvBytes[1] == 5)
HookManager.Log("connection refused by destination host");
if (recvBytes[1] == 6)
HookManager.Log("TTL expired");
if (recvBytes[1] == 7)
HookManager.Log("command not supported / protocol error");
if (recvBytes[1] == 8)
HookManager.Log("address type not supported");
HookManager.Log("Proxy: Connection error binding eve server");
return false;
}
return true;
}
public void Dispose()
{
if (_hook == null)
return;
_hook.Dispose();
_hook = null;
}
}
}
Related
I'm creating a wrapper for an unmanaged C++ function to be called using C#.
The function returns a vector of structs.
When returning from the function, the strings are all ok, but after the 'return' to the wrapper, the strings break returning weird characters.
The weird thing is, if I call it a second time, without closing Visual Studio, it works!
I have saved all the files as Unicode UTF-8, set Unicode as the character set on the Visual Studio project, defined UNICODE and _UNICODE, but still the problem persists.
This is the unmanaged struct:
typedef struct SessionEnumOutput {
SessionEnumOutput() {};
wchar_t *UserName;
wchar_t *SessionName;
WtsSessionState SessionState;
}SessionEnumOutput, *PSessionEnumOutput;
It being built on the unmanaged function:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
The managed wrapper:
ref class wSessionEnumOutput
{
public:
String^ UserName;
String^ SessionName;
wWtsSessionState SessionState;
};
List<wSessionEnumOutput^>^ GetEnumeratedSession(String^ computerName, bool onlyActive, bool excludeSystemSessions)
{
pin_ptr<const wchar_t> wName = PtrToStringChars(computerName);
List<wSessionEnumOutput^>^ output = gcnew List<wSessionEnumOutput^>();
vector<Unmanaged::SessionEnumOutput> *result = new vector<Unmanaged::SessionEnumOutput>;
*result = ptr->GetEnumeratedSession((LPWSTR)wName, onlyActive, excludeSystemSessions);
for (size_t it = 0; it < result->size(); it++)
{
Unmanaged::SessionEnumOutput single = result->at(it);
wSessionEnumOutput^ inner = gcnew wSessionEnumOutput();
inner->UserName = Marshal::PtrToStringUni((IntPtr)single.UserName);
inner->SessionName = Marshal::PtrToStringUni((IntPtr)single.SessionName);
inner->SessionState = (wWtsSessionState)single.SessionState;
output->Add(inner);
}
I can see the strings broken at *vectorUnmanaged::SessionEnumOutput result = new vectorUnmanaged::SessionEnumOutput;
I have created a test console to call the C# function twice to analyze the heap:
List<Managed.wSessionEnumOutput> thing = Utilities.GetComputerSession();
Console.WriteLine("###################################################");
for (int i = 0; i < thing.Count; i++)
{
Console.WriteLine("User name: " + thing[i].UserName);
Console.WriteLine("Session name: " + thing[i].SessionName);
Console.WriteLine("Session state: " + thing[i].SessionState);
Console.WriteLine("###################################################");
}
Console.WriteLine("\n");
thing = Utilities.GetComputerSession();
Console.WriteLine("###################################################");
for (int i = 0; i < thing.Count; i++)
{
Console.WriteLine("User name: " + thing[i].UserName);
Console.WriteLine("Session name: " + thing[i].SessionName);
Console.WriteLine("Session state: " + thing[i].SessionState);
Console.WriteLine("###################################################");
}
The difference is, on the second call, I can see Unicode and UTF-8 decoders loaded on the heap.
On the first call, they are not there.
here are both call's results:
I'm not a developer, just a curious system administrator, so pardon my coding habilities.
What am I missing?
EDIT:
Function definition:
vector<Unmanaged::SessionEnumOutput> Unmanaged::GetEnumeratedSession(
LPWSTR computerName = NULL,
BOOL onlyActive = 0,
BOOL excludeSystemSessions = 0
)
{
HANDLE session;
BOOL enumResult;
DWORD pCount = 0;
DWORD pLevel = 1;
vector<SessionEnumOutput> output;
PWTS_SESSION_INFO_1 sessionInfo = (PWTS_SESSION_INFO_1)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WTS_SESSION_INFO_1));
if (computerName != NULL)
{
session = WTSOpenServer(computerName);
if (session == NULL) { goto END; }
}
else { session = WTS_CURRENT_SERVER_HANDLE; }
enumResult = WTSEnumerateSessionsEx(session, &pLevel, 0, &sessionInfo, &pCount);
if (enumResult == 0) { goto END; }
switch (onlyActive)
{
case 1:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
break;
default:
if (excludeSystemSessions == 0)
{
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
wchar_t* sessionName;
wchar_t* sessUserName;
SessionEnumOutput outObj;
if (innerSes.pUserName == NULL) { sessUserName = L"System"; }
else { sessUserName = innerSes.pUserName; }
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
else
{
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
wstring sessUserName;
if (innerSes.pUserName == NULL) { sessUserName = L""; }
else { sessUserName = innerSes.pUserName; }
if (sessUserName.length() > 0)
{
wchar_t *innerUser = (wchar_t*)sessUserName.c_str();
wchar_t *sessionName;
SessionEnumOutput outObj;
WTS_SESSION_INFO_1 innerSes = sessionInfo[i];
if (innerSes.pSessionName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pSessionName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = innerUser;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
}
}
}
break;
}
END:
if (session != NULL) { WTSCloseServer(session); }
if (pCount > 0) { WTSFreeMemoryEx(WTSTypeSessionInfoLevel1, sessionInfo, pCount); }
return output;
}
Solved the mystery!
Mr. CharlieFace on the comments posted another question where they are discussing the unespected behavior of the function WTSEnumerateSessionsEx.
Unfortunately, this is an issue happening on Windows for some time now.
I've followed the approach of calling WTSEnumerateSessions and then WTSQuerySessionInformation to get the user name.
Fragment:
PWTS_SESSION_INFO sessionInfo = (PWTS_SESSION_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WTS_SESSION_INFO));
if (computerName != NULL)
{
session = WTSOpenServer(computerName);
if (session == NULL) { goto END; }
}
else { session = WTS_CURRENT_SERVER_HANDLE; }
enumResult = WTSEnumerateSessions(session, 0, 1, &sessionInfo, &pCount);
if (enumResult == 0) { goto END; }
switch (onlyActive)
{
case 1:
for (DWORD i = 0; i < pCount; i++)
{
WTS_SESSION_INFO innerSes = sessionInfo[i];
if (innerSes.State == WTSActive)
{
wchar_t *sessionName;
wchar_t *sessUserName;
LPWSTR ppBuffer;
DWORD pBytesReturned;
BOOL thisResult;
thisResult = WTSQuerySessionInformation(session, innerSes.SessionId, WTSUserName, &ppBuffer, &pBytesReturned);
if (thisResult == 0) { goto END; }
if (ppBuffer == NULL) { sessUserName = L"System"; }
else { sessUserName = ppBuffer; }
if (innerSes.pWinStationName == NULL) { sessionName = L""; }
else { sessionName = innerSes.pWinStationName; }
Unmanaged::SessionEnumOutput inner;
inner.UserName = sessUserName;
inner.SessionName = sessionName;
inner.SessionState = (WtsSessionState)innerSes.State;
output.push_back(inner);
WTSFreeMemory(&ppBuffer);
}
}
break;
Look how pretty that is!
Sources:
Why does WTSFreeMemoryExA always return ERROR_INVALID_PARAMETER when passed a WTSTypeClass of WTSTypeSessionInfoLevel1?
Memory leak issues with Windows API call - Delphi
Thank you very much for the help!
As I mentioned, there is an outstanding bug involving the WTS functions when used with the ANSI versions. Instead you should use the Unicode versions.
See Why does WTSFreeMemoryExA always return ERROR_INVALID_PARAMETER when passed a WTSTypeClass of WTSTypeSessionInfoLevel1?
I think using a C++/CLI wrapper for this to be able to use in C# is overkill. You should be able to do this using standard PInvoke marshalling in C#.
It's best not to rely on BestFitMapping, and instead specify the function names explicitly.
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern IntPtr WTSOpenServerExW (string pServerName);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern void WTSCloseServer(IntPtr hServer);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern bool WTSEnumerateSessionsExW(
IntPtr hServer,
ref int pLevel,
int Filter,
out IntPtr ppSessionInfo,
out int pCount
);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, SetLastError = true)]
static extern bool WTSFreeMemoryExW(
WTS_TYPE_CLASS WTSTypeClass,
IntPtr pMemory,
int NumberOfEntries
);
enum WTS_TYPE_CLASS
{
WTSTypeProcessInfoLevel0,
WTSTypeProcessInfoLevel1,
WTSTypeSessionInfoLevel1
}
enum WtsSessionState
{
WTSActive,
WTSConnected,
WTSConnectQuery,
WTSShadow,
WTSDisconnected,
WTSIdle,
WTSListen,
WTSReset,
WTSDown,
WTSInit
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct WTS_SESSION_INFO
{
public int ExecEnvId;
public WtsSessionState State;
public int SessionId;
[MarshalAs(UnmanagedType.LPTStr)]
public string pSessionName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pHostName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDomainName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pFarmName;
}
List<SessionEnumOutput> GetEnumeratedSession(
string computerName = null,
bool onlyActive = false,
bool excludeSystemSessions = false
)
{
IntPtr server = default;
IntPtr sessionInfo = default;
int pCount = default;
List<SessionEnumOutput> output = new List<SessionEnumOutput>();
if (computerName != null)
{
server = WTSOpenServerExW(computerName);
if (server == IntPtr.Zero || server == new IntPtr(-1))
throw new Exception("Invalid computer name");
}
try
{
int pLevel = 1;
if (!WTSEnumerateSessionsExW(server, ref pLevel, 0, out sessionInfo, out pCount))
throw new Win32Exception(Marshal.GetLastWin32Error());
for (var i = 0; i < pCount; i++)
{
WTS_SESSION_INFO innerSes = Marshal.PtrToStructure<WTS_SESSION_INFO>(sessionInfo + i * Marshal.SizeOf<WTS_SESSION_INFO>());
if (onlyActive && innerSes.State != WtsSessionState.WTSActive
|| excludeSystemSessions && innerSes.pSessionName == null)
continue;
SessionEnumOutput inner = new SessionEnumOutput
{
UserName = innerSes.pUserName ?? "System",
SessionName = innerSes.pSessionName ?? "",
SessionState = innerSes.State,
};
output.Add(inner);
};
}
finally
{
if (sessionInfo != default)
WTSFreeMemoryExW(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, sessionInfo, pCount);
if (server != default)
WTSCloseServer(server);
}
return output;
}
Note the use of a finally to enure memory is freed correctly.
There is also a huge amount of duplicated code. I have cleaned up the rest of the function significantly.
ZEBRA PRINTERS uses the native commands called ZPL programming language, printing PDF to the printers usually do not work, the best solution for printing using C# is?
I created this question and answer as I could not find an effective solution on the Internet, and this one will help a lot of people with this issue
The best solution below:
Requirement: Install the free library Ghostscript 32 bits in the computer
https://www.ghostscript.com/download.html
Main methods: pdfbase64 to ZPL or Stream pdf to ZPL
public static List<string> ZplFromPdf(string pdfBase64, int dpi = 300)
{
return ZplFromPdf(new MemoryStream(Convert.FromBase64String(pdfBase64)), new Size(0,0), dpi);
}
public static List<string> ZplFromPdf(Stream pdf, Size size, int dpi = 300)
{
var zpls = new List<string>();
if (size == new Size(0, 0))
{
size = new Size(812, 1218);
}
using (var rasterizer = new GhostscriptRasterizer())
{
rasterizer.Open(pdf);
var images = new List<Image>();
for (int pageNumber = 1; pageNumber <= rasterizer.PageCount; pageNumber++)
{
var bmp = new Bitmap(rasterizer.GetPage(dpi, dpi, pageNumber), size.Width, size.Height);
var zpl = new StringBuilder();
zpl.Append(ZPLHelper.GetGrfStoreCommand("R:LBLRA2.GRF", bmp));
zpl.Append("^XA^FO0,0^XGR:LBLRA2.GRF,1,1^FS^XZ");
zpl.Append("^XA^IDR:LBLRA2.GRF^FS^XZ");
zpls.Add(zpl.ToString());
}
return zpls;
}
}
Core methods
public class ZPLHelper
{
static Regex regexFilename = new Regex("^[REBA]:[A-Z0-9]{1,8}\\.GRF$");
public static bool PrintLabelBase64Image(string printerName, string base64Image, string jobName = "label")
{
try
{
var bmpLabel = Base64ToBitmap(base64Image);
var baseStream = new MemoryStream();
var tw = new StreamWriter(baseStream, Encoding.UTF8);
tw.WriteLine(GetGrfStoreCommand("R:LBLRA2.GRF", bmpLabel));
tw.WriteLine(GetGrfPrintCommand("R:LBLRA2.GRF"));
tw.WriteLine(GetGrfDeleteCommand("R:LBLRA2.GRF"));
tw.Flush();
baseStream.Position = 0;
var gdipj = new GdiPrintJob(printerName, GdiPrintJobDataType.Raw, jobName, null);
gdipj.WritePage(baseStream);
gdipj.CompleteJob();
return true;
}
catch (Exception)
{
return false;
}
}
public static bool PrintLabelZpl(string printerName, string zplCommand, string jobName = "label")
{
var baseStream = new MemoryStream();
var tw = new StreamWriter(baseStream, Encoding.UTF8);
tw.WriteLine(zplCommand);
tw.Flush();
baseStream.Position = 0;
var gdiJob = new GdiPrintJob(printerName, GdiPrintJobDataType.Raw, jobName, null);
gdiJob.WritePage(baseStream);
gdiJob.CompleteJob();
return true;
}
private static Bitmap Base64ToBitmap(string base64Image)
{
Image image;
using (var ms = new MemoryStream(Convert.FromBase64String(base64Image)))
{
image = Image.FromStream(ms);
}
return new Bitmap(image);
}
public static string GetGrfStoreCommand(string filename, Bitmap bmpSource)
{
if (bmpSource == null)
{
throw new ArgumentNullException("bmpSource");
}
validateFilename(filename);
var dim = new Rectangle(Point.Empty, bmpSource.Size);
var stride = ((dim.Width + 7) / 8);
var bytes = stride * dim.Height;
using (var bmpCompressed = bmpSource.Clone(dim, PixelFormat.Format1bppIndexed))
{
var result = new StringBuilder();
result.AppendFormat("^XA~DG{2},{0},{1},", stride * dim.Height, stride, filename);
byte[][] imageData = GetImageData(dim, stride, bmpCompressed);
byte[] previousRow = null;
foreach (var row in imageData)
{
appendLine(row, previousRow, result);
previousRow = row;
}
result.Append(#"^FS^XZ");
return result.ToString();
}
}
public static string GetGrfDeleteCommand(string filename)
{
validateFilename(filename);
return string.Format("^XA^ID{0}^FS^XZ", filename);
}
public static string GetGrfPrintCommand(string filename)
{
validateFilename(filename);
return string.Format("^XA^FO0,0^XG{0},1,1^FS^XZ", filename);
}
private static void validateFilename(string filename)
{
if (!regexFilename.IsMatch(filename))
{
throw new ArgumentException("Filename must be in the format "
+ "R:XXXXXXXX.GRF. Drives are R, E, B, A. Filename can "
+ "be alphanumeric between 1 and 8 characters.", "filename");
}
}
unsafe private static byte[][] GetImageData(Rectangle dim, int stride, Bitmap bmpCompressed)
{
byte[][] imageData;
var data = bmpCompressed.LockBits(dim, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
try
{
byte* pixelData = (byte*)data.Scan0.ToPointer();
byte rightMask = (byte)(0xff << (data.Stride * 8 - dim.Width));
imageData = new byte[dim.Height][];
for (int row = 0; row < dim.Height; row++)
{
byte* rowStart = pixelData + row * data.Stride;
imageData[row] = new byte[stride];
for (int col = 0; col < stride; col++)
{
byte f = (byte)(0xff ^ rowStart[col]);
f = (col == stride - 1) ? (byte)(f & rightMask) : f;
imageData[row][col] = f;
}
}
}
finally
{
bmpCompressed.UnlockBits(data);
}
return imageData;
}
private static void appendLine(byte[] row, byte[] previousRow, StringBuilder baseStream)
{
if (row.All(r => r == 0))
{
baseStream.Append(",");
return;
}
if (row.All(r => r == 0xff))
{
baseStream.Append("!");
return;
}
if (previousRow != null && MatchByteArray(row, previousRow))
{
baseStream.Append(":");
return;
}
byte[] nibbles = new byte[row.Length * 2];
for (int i = 0; i < row.Length; i++)
{
nibbles[i * 2] = (byte)(row[i] >> 4);
nibbles[i * 2 + 1] = (byte)(row[i] & 0x0f);
}
for (int i = 0; i < nibbles.Length; i++)
{
byte cPixel = nibbles[i];
int repeatCount = 0;
for (int j = i; j < nibbles.Length && repeatCount <= 400; j++)
{
if (cPixel == nibbles[j])
{
repeatCount++;
}
else
{
break;
}
}
if (repeatCount > 2)
{
if (repeatCount == nibbles.Length - i
&& (cPixel == 0 || cPixel == 0xf))
{
if (cPixel == 0)
{
if (i % 2 == 1)
{
baseStream.Append("0");
}
baseStream.Append(",");
return;
}
else if (cPixel == 0xf)
{
if (i % 2 == 1)
{
baseStream.Append("F");
}
baseStream.Append("!");
return;
}
}
else
{
baseStream.Append(getRepeatCode(repeatCount));
i += repeatCount - 1;
}
}
baseStream.Append(cPixel.ToString("X"));
}
}
private static string getRepeatCode(int repeatCount)
{
if (repeatCount > 419)
throw new ArgumentOutOfRangeException();
int high = repeatCount / 20;
int low = repeatCount % 20;
const string lowString = " GHIJKLMNOPQRSTUVWXY";
const string highString = " ghijklmnopqrstuvwxyz";
string repeatStr = "";
if (high > 0)
{
repeatStr += highString[high];
}
if (low > 0)
{
repeatStr += lowString[low];
}
return repeatStr;
}
private static bool MatchByteArray(byte[] row, byte[] previousRow)
{
for (int i = 0; i < row.Length; i++)
{
if (row[i] != previousRow[i])
{
return false;
}
}
return true;
}
}
internal static class NativeMethods
{
#region winspool.drv
#region P/Invokes
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool OpenPrinter(string szPrinter, out IntPtr hPrinter, IntPtr pd);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern UInt32 StartDocPrinter(IntPtr hPrinter, Int32 level, IntPtr di);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern bool WritePrinter(
// 0
IntPtr hPrinter,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pBytes,
// 2
UInt32 dwCount,
out UInt32 dwWritten);
#endregion
#region Structs
[StructLayout(LayoutKind.Sequential)]
internal struct DOC_INFO_1
{
[MarshalAs(UnmanagedType.LPWStr)]
public string DocName;
[MarshalAs(UnmanagedType.LPWStr)]
public string OutputFile;
[MarshalAs(UnmanagedType.LPWStr)]
public string Datatype;
}
#endregion
#endregion
}
/// <summary>
/// Represents a print job in a spooler queue
/// </summary>
public class GdiPrintJob
{
IntPtr PrinterHandle;
//IntPtr DocHandle;
/// <summary>
/// The ID assigned by the print spooler to identify the job
/// </summary>
public UInt32 PrintJobID { get; private set; }
/// <summary>
/// Create a print job with a enumerated datatype
/// </summary>
/// <param name="PrinterName"></param>
/// <param name="dataType"></param>
/// <param name="jobName"></param>
/// <param name="outputFileName"></param>
public GdiPrintJob(string PrinterName, GdiPrintJobDataType dataType, string jobName, string outputFileName)
: this(PrinterName, translateType(dataType), jobName, outputFileName)
{
}
/// <summary>
/// Create a print job with a string datatype
/// </summary>
/// <param name="PrinterName"></param>
/// <param name="dataType"></param>
/// <param name="jobName"></param>
/// <param name="outputFileName"></param>
public GdiPrintJob(string PrinterName, string dataType, string jobName, string outputFileName)
{
if (string.IsNullOrWhiteSpace(PrinterName))
throw new ArgumentNullException("PrinterName");
if (string.IsNullOrWhiteSpace(dataType))
throw new ArgumentNullException("PrinterName");
IntPtr hPrinter;
if (!NativeMethods.OpenPrinter(PrinterName, out hPrinter, IntPtr.Zero))
throw new Win32Exception();
this.PrinterHandle = hPrinter;
NativeMethods.DOC_INFO_1 docInfo = new NativeMethods.DOC_INFO_1()
{
DocName = jobName,
Datatype = dataType,
OutputFile = outputFileName
};
IntPtr pDocInfo = Marshal.AllocHGlobal(Marshal.SizeOf(docInfo));
RuntimeHelpers.PrepareConstrainedRegions();
try
{
Marshal.StructureToPtr(docInfo, pDocInfo, false);
UInt32 docid = NativeMethods.StartDocPrinter(hPrinter, 1, pDocInfo);
if (docid == 0)
throw new Win32Exception();
this.PrintJobID = docid;
}
finally
{
Marshal.FreeHGlobal(pDocInfo);
}
}
/// <summary>
/// Write the data of a single page or a precomposed PCL document
/// </summary>
/// <param name="data"></param>
public void WritePage(Stream data)
{
if (data == null)
throw new ArgumentNullException("data");
if (!data.CanRead && !data.CanWrite)
throw new ObjectDisposedException("data");
if (!data.CanRead)
throw new NotSupportedException("stream is not readable");
if (!NativeMethods.StartPagePrinter(this.PrinterHandle))
throw new Win32Exception();
byte[] buffer = new byte[0x14000]; /* 80k is Stream.CopyTo default */
uint read = 1;
while ((read = (uint)data.Read(buffer, 0, buffer.Length)) != 0)
{
UInt32 written;
if (!NativeMethods.WritePrinter(this.PrinterHandle, buffer, read, out written))
throw new Win32Exception();
if (written != read)
throw new InvalidOperationException("Error while writing to stream");
}
if (!NativeMethods.EndPagePrinter(this.PrinterHandle))
throw new Win32Exception();
}
/// <summary>
/// Complete the current job
/// </summary>
public void CompleteJob()
{
if (!NativeMethods.EndDocPrinter(this.PrinterHandle))
throw new Win32Exception();
}
#region datatypes
private readonly static string[] dataTypes = new string[]
{
// 0
null,
"RAW",
// 2
"RAW [FF appended]",
"RAW [FF auto]",
// 4
"NT EMF 1.003",
"NT EMF 1.006",
// 6
"NT EMF 1.007",
"NT EMF 1.008",
// 8
"TEXT",
"XPS_PASS",
// 10
"XPS2GDI"
};
private static string translateType(GdiPrintJobDataType type)
{
return dataTypes[(int)type];
}
#endregion
}
public enum GdiPrintJobDataType
{
Unknown = 0,
Raw = 1,
RawAppendFF = 2,
RawAuto = 3,
NtEmf1003 = 4,
NtEmf1006 = 5,
NtEmf1007 = 6,
NtEmf1008 = 7,
Text = 8,
XpsPass = 9,
Xps2Gdi = 10
}
You might be interested in my NuGet package for converting PDF files into ZPL code.
The official Zebra SDK might contain a conversion but I haven't checked (not sure about the licensing here).
Adapting your previous code for the first page would be
public static string ZplFromPdf(string pdfBase64, int dpi = 300)
{
return PDFtoZPL.Conversion.ConvertPdfPage(pdfBase64, dpi: dpi);
}
Disclaimer: I ran into the same problem, stitched together ZPL code found on the internet and bundled it into a tiny .NET API.
I'm building a .Net application that should import a VS2010/2008 solution and find the start up project by reading the .suo file. I looked at How do I programmatically find out the Action of each StartUp Project in a solution? but the solution mentioned works only if I select Multiple Startup Projects in the Solution Properties window. My solution does not have multiple startup project. Is there a way to find the startup project when the solution has only 1 startup proj?
Here's the code
public static class StartUpProjectHelper
{
public static FileInfo GetStartUpProject(FileInfo solutionFile)
{
FileInfo startUpProject = null;
string suoFile = solutionFile.FullName.Substring(0, solutionFile.FullName.Length - 4) + ".suo";
string guid = null;
bool found = false;
foreach (var kvp in ReadStartupOptions(suoFile))
{
if (((kvp.Value & 1) != 0 || (kvp.Value & 2) != 0) && !found)
{
guid = kvp.Key.ToString();
found = true;
}
}
if (!string.IsNullOrEmpty(guid))
{
string projectname = GetProjectNameFromGuid(solutionFile, guid).Trim().TrimStart('\"').TrimEnd('\"');
startUpProject = new FileInfo(Path.Combine(solutionFile.DirectoryName, projectname));
}
return startUpProject;
}
public static string GetProjectNameFromGuid(FileInfo solutionFile, string guid)
{
string projectName = null;
using (var reader = new StreamReader(solutionFile.FullName))
{
string line;
bool found = false;
while ((line = reader.ReadLine()) != null && !found)
{
// sample format
//Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saltus.digiTICKET.laptop", "digiTICKET.laptop\Saltus.digiTICKET.laptop.csproj",
//"{236D51A1-DEB7-41C3-A4C1-1D16D0A85382}" EndProject
if ((line.IndexOf(guid.ToUpper()) > -1) && line.Contains(",") && line.Contains("="))
{
projectName = line.Split(',')[1].Split(',')[0];
found = true;
}
}
}
return projectName;
}
//// from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut
public static IDictionary<Guid, int> ReadStartupOptions(string filePath)
{
if (filePath == null)
{
throw new InvalidOperationException("No file selected");
}
// look for this token in the file
const string token = "dwStartupOpt\0=";
byte[] tokenBytes = Encoding.Unicode.GetBytes(token);
var dic = new Dictionary<Guid, int>();
byte[] bytes;
using (var stream = new MemoryStream())
{
ExtractStream(filePath, "SolutionConfiguration", stream);
bytes = stream.ToArray();
}
int i = 0;
do
{
bool found = true;
for (int j = 0; j < tokenBytes.Length; j++)
{
if (bytes[i + j] != tokenBytes[j])
{
found = false;
break;
}
}
if (found)
{
// back read the corresponding project guid
// guid is formatted as {guid}
// len to read is Guid length* 2 and there are two offset bytes between guid and startup options token
var guidBytes = new byte[38*2];
Array.Copy(bytes, i - guidBytes.Length - 2, guidBytes, 0, guidBytes.Length);
var guid = new Guid(Encoding.Unicode.GetString(guidBytes));
// skip VT_I4
int options = BitConverter.ToInt32(bytes, i + tokenBytes.Length + 2);
dic[guid] = options;
}
i++;
} while (i < bytes.Length);
return dic;
}
public static void ExtractStream(string filePath, string streamName, Stream output)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
if (streamName == null)
throw new ArgumentNullException("streamName");
if (output == null)
throw new ArgumentNullException("output");
IStorage storage;
int hr = StgOpenStorage(filePath, null, STGM.READ | STGM.SHARE_DENY_WRITE, IntPtr.Zero, 0, out storage);
if (hr != 0)
throw new Win32Exception(hr);
try
{
IStream stream;
hr = storage.OpenStream(streamName, IntPtr.Zero, STGM.READ | STGM.SHARE_EXCLUSIVE, 0, out stream);
if (hr != 0)
throw new Win32Exception(hr);
int read = 0;
IntPtr readPtr = Marshal.AllocHGlobal(Marshal.SizeOf(read));
try
{
var bytes = new byte[0x1000];
do
{
stream.Read(bytes, bytes.Length, readPtr);
read = Marshal.ReadInt32(readPtr);
if (read == 0)
break;
output.Write(bytes, 0, read);
} while (true);
}
finally
{
Marshal.FreeHGlobal(readPtr);
Marshal.ReleaseComObject(stream);
}
}
finally
{
Marshal.ReleaseComObject(storage);
}
}
[DllImport("ole32.dll")]
private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved,
out IStorage ppstgOpen);
#region Nested type: IStorage
[ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IStorage
{
void Unimplemented0();
[PreserveSig]
int OpenStream([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr reserved1, STGM grfMode,
uint reserved2, out IStream ppstm);
// other methods not declared for simplicity
}
#endregion
#region Nested type: STGM
[Flags]
private enum STGM
{
READ = 0x00000000,
SHARE_DENY_WRITE = 0x00000020,
SHARE_EXCLUSIVE = 0x00000010,
// other values not declared for simplicity
}
#endregion
}
//// from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut``
Here's the solution. It is mostly based on #SimonMourier post, but instead of searching for "dwStartupOpt\0=", I'm looking for "StartupProject\0=&\0". This token occurs only once in the .suo file and is followed by the startup project's GUID.
public static FileInfo GetStartUpProject(FileInfo solutionFile)
{
FileInfo startUpProject = null;
string projectName = Path.GetFileNameWithoutExtension(solutionFile.FullName);
FileInfo suoFileInfo = new FileInfo(Path.Combine(solutionFile.Directory.FullName, string.Format(projectName + "{0}", ".suo")));
string guid = ReadStartupOptions(suoFileInfo.FullName).ToString();
if (!string.IsNullOrEmpty(guid))
{
string projectname = GetProjectNameFromGuid(solutionFile, guid).Trim().TrimStart('\"').TrimEnd('\"');
startUpProject = new FileInfo(Path.Combine(solutionFile.DirectoryName, projectname));
}
return startUpProject;
}
public static string GetProjectNameFromGuid(FileInfo solutionFile, string guid)
{
string projectName = null;
using (var reader = new StreamReader(solutionFile.FullName))
{
string line;
bool found = false;
while ((line = reader.ReadLine()) != null && !found)
{
if ((line.IndexOf(guid.ToUpper()) > -1) && line.Contains(",") && line.Contains("="))
{
projectName = line.Split(',')[1].Split(',')[0];
found = true;
}
}
}
return projectName;
}
// from https://stackoverflow.com/questions/8817693/how-do-i-programmatically-find-out-the-action-of-each-startup-project-in-a-solut
public static Guid ReadStartupOptions(string filePath)
{
Guid guid = new Guid();
if (filePath == null)
{
throw new InvalidOperationException("No file selected");
}
const string token = "StartupProject\0=&\0";
byte[] tokenBytes = Encoding.Unicode.GetBytes(token);
byte[] bytes;
using (var stream = new MemoryStream())
{
ExtractStream(filePath, "SolutionConfiguration", stream);
bytes = stream.ToArray();
}
var guidBytes = new byte[36 * 2];
for (int i2 = 0; i2 < bytes.Length; i2++)
{
if (bytes.Skip(i2).Take(tokenBytes.Length).SequenceEqual(tokenBytes))
{
Array.Copy(bytes, i2 + tokenBytes.Length + 2, guidBytes, 0, guidBytes.Length);
guid = new Guid(Encoding.Unicode.GetString(guidBytes));
break;
}
}
return guid;
}
public static void ExtractStream(string filePath, string streamName, Stream output)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
if (streamName == null)
throw new ArgumentNullException("streamName");
if (output == null)
throw new ArgumentNullException("output");
IStorage storage;
int hr = StgOpenStorage(filePath, null, STGM.READ | STGM.SHARE_DENY_WRITE, IntPtr.Zero, 0, out storage);
if (hr != 0)
throw new Win32Exception(hr);
try
{
IStream stream;
hr = storage.OpenStream(streamName, IntPtr.Zero, STGM.READ | STGM.SHARE_EXCLUSIVE, 0, out stream);
if (hr != 0)
throw new Win32Exception(hr);
int read = 0;
IntPtr readPtr = Marshal.AllocHGlobal(Marshal.SizeOf(read));
try
{
var bytes = new byte[0x1000];
do
{
stream.Read(bytes, bytes.Length, readPtr);
read = Marshal.ReadInt32(readPtr);
if (read == 0)
break;
output.Write(bytes, 0, read);
} while (true);
}
finally
{
Marshal.FreeHGlobal(readPtr);
Marshal.ReleaseComObject(stream);
}
}
finally
{
Marshal.ReleaseComObject(storage);
}
}
[DllImport("ole32.dll")]
private static extern int StgOpenStorage([MarshalAs(UnmanagedType.LPWStr)] string pwcsName,
IStorage pstgPriority, STGM grfMode, IntPtr snbExclude, uint reserved,
out IStorage ppstgOpen);
#region Nested type: IStorage
[ComImport, Guid("0000000b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IStorage
{
void Unimplemented0();
[PreserveSig]
int OpenStream([MarshalAs(UnmanagedType.LPWStr)] string pwcsName, IntPtr reserved1, STGM grfMode,
uint reserved2, out IStream ppstm);
// other methods not declared for simplicity
}
#endregion
#region Nested type: STGM
[Flags]
private enum STGM
{
READ = 0x00000000,
SHARE_DENY_WRITE = 0x00000020,
SHARE_EXCLUSIVE = 0x00000010,
// other values not declared for simplicity
}
#endregion
Below is the code sample which I got from online resource but it's suppose to work with fullframework, but when I try to build it using C# smart device, it throws exception saying it's out of memory. Does anybody know how can I fix it to use on compact? the out of memory exception when I make the second call to VerQueryValue which is the last one.
thanks,
[DllImport("coredll.dll")]
public static extern bool VerQueryValue(byte[] buffer, string subblock, out IntPtr blockbuffer, out uint len);
[DllImport("coredll.dll")]
public static extern bool VerQueryValue(byte[] pBlock, string pSubBlock, out string pValue, out uint len);
//
private static void GetAssemblyVersion()
{
string filename = #"\Windows\MyLibrary.dll";
if (File.Exists(filename))
{
try {
int handle = 0;
Int32 size = 0;
size = GetFileVersionInfoSize(filename, out handle);
if (size > 0)
{
bool retValue;
byte[] buffer = new byte[size];
retValue = GetFileVersionInfo(filename, handle, size, buffer);
if (retValue == true)
{
bool success = false;
IntPtr blockbuffer = IntPtr.Zero;
uint len = 0;
//success = VerQueryValue(buffer, "\\", out blockbuffer, out len);
success = VerQueryValue(buffer, #"\VarFileInfo\Translation", out blockbuffer, out len);
if(success)
{
int p = (int)blockbuffer;
//Reads a 16-bit signed integer from unmanaged memory
int j = Marshal.ReadInt16((IntPtr)p);
p += 2;
//Reads a 16-bit signed integer from unmanaged memory
int k = Marshal.ReadInt16((IntPtr)p);
string sb = string.Format("{0:X4}{1:X4}", j, k);
string spv = #"\StringFileInfo\" + sb + #"\ProductVersion";
string versionInfo;
VerQueryValue(buffer, spv, out versionInfo, out len);
}
}
}
}
catch (Exception err)
{
string error = err.Message;
}
}
}
After adding these two statements:
Int32 dwVerMinor = j & 0xffff;
Int32 dwVerBuild = k & 0xffff;
it's able to retrieve the DLL version.
Here's an implementation:
using DWORD = System.UInt32;
public static class NativeFile
{
public struct NativeFileInfo
{
public Version Version;
public NameValueCollection StringTable;
}
public unsafe static NativeFileInfo GetFileInfo(string path)
{
if (!File.Exists(path))
{
throw new FileNotFoundException();
}
IntPtr handle;
var size = GetFileVersionInfoSize(path, out handle);
var buffer = Marshal.AllocHGlobal(size);
try
{
if (!GetFileVersionInfo(path, handle, size, buffer))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
IntPtr pVersion;
int versionLength;
VerQueryValue(buffer, “\”, out pVersion, out versionLength);
var versionInfo = (VS_FIXEDFILEINFO)Marshal.PtrToStructure(pVersion, typeof(VS_FIXEDFILEINFO));
var version = new Version((int)versionInfo.dwFileVersionMS >> 16,
(int)versionInfo.dwFileVersionMS & 0xFFFF,
(int)versionInfo.dwFileVersionLS >> 16,
(int)versionInfo.dwFileVersionLS & 0xFFFF);
// move to the string table and parse
var pStringTable = ((byte*)pVersion.ToPointer()) + versionLength;
var strings = ParseStringTable(pStringTable, size – versionLength);
return new NativeFileInfo
{
Version = version,
StringTable = strings
};
}
finally
{
Marshal.FreeHGlobal(buffer);
}
}
private unsafe static NameValueCollection ParseStringTable(byte* pStringTable, int length)
{
NameValueCollection nvc = new NameValueCollection();
byte* p = pStringTable;
short stringFileInfoLength = (short)*p;
byte* end = pStringTable + length;
p += (2 + 2 + 2); // length + valuelength + type
// verify key
var key = Marshal.PtrToStringUni(new IntPtr(p), 14);
if (key != "StringFileInfo") throw new ArgumentException();
// move past the key to the first string table
p += 30;
short stringTableLength = (short)*p;
p += (2 + 2 + 2); // length + valuelength + type
// get locale info
key = Marshal.PtrToStringUni(new IntPtr(p), 8);
// move to the first string
p += 18;
while (p < end)
{
short stringLength = (short)*p;
p += 2;
short valueChars = (short)*p;
p += 2;
short type = (short)*p;
p += 2;
if (stringLength == 0) break;
if ((valueChars == 0) || (type != 1))
{
p += stringLength;
continue;
}
var keyLength = stringLength – (valueChars * 2) – 6;
key = Marshal.PtrToStringUni(new IntPtr(p), keyLength / 2).TrimEnd(”);
p += keyLength;
var value = Marshal.PtrToStringUni(new IntPtr(p), valueChars).TrimEnd(”);
p += valueChars * 2;
if ((int)p % 4 != 0) p += 2;
nvc.Add(key, value);
}
return nvc;
}
private const string COREDLL = "coredll.dll";
[DllImport(COREDLL, SetLastError = true)]
private static extern int GetFileVersionInfoSize(string lptstrFilename, out IntPtr lpdwHandle);
[DllImport(COREDLL, SetLastError = true)]
private static extern bool GetFileVersionInfo(string lptstrFilename, IntPtr dwHandle, int dwLen, IntPtr lpData);
[DllImport(COREDLL, SetLastError = true)]
private static extern bool VerQueryValue(IntPtr pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);
[StructLayout(LayoutKind.Sequential)]
private struct VS_FIXEDFILEINFO
{
public DWORD dwSignature;
public DWORD dwStrucVersion;
public DWORD dwFileVersionMS;
public DWORD dwFileVersionLS;
public DWORD dwProductVersionMS;
public DWORD dwProductVersionLS;
public DWORD dwFileFlagsMask;
public DWORD dwFileFlags;
public FileOS dwFileOS;
public FileType dwFileType;
public DWORD dwFileSubtype;
public DWORD dwFileDateMS;
public DWORD dwFileDateLS;
};
public enum FileOS : uint
{
Unknown = 0x00000000,
DOS = 0x00010000,
OS2_16 = 0x00020000,
OS2_32 = 0x00030000,
NT = 0x00040000,
WindowsCE = 0x00050000,
}
public enum FileType : uint
{
Unknown = 0x00,
Application = 0x01,
DLL = 0x02,
Driver = 0x03,
Font = 0x04,
VXD = 0x05,
StaticLib = 0x07
}
}
And an example of usage:
class Program
{
static void Main(string[] args)
{
string target = “\FlashFX Disk\ARMv4i\conmanclient2.exe”;
var version = NativeFile.GetFileInfo(target);
Console.WriteLine(string.Format(“File: { 0}”, Path.GetFileName(target)));
Console.WriteLine(string.Format(“Version: { 0}”, version.Version.ToString(4)));
foreach (var key in version.StringTable.AllKeys)
{
Console.WriteLine(string.Format(“{ 0}: { 1}”, key, version.StringTable[key]));
}
Console.ReadLine();
}
Here's some C# source code which implements an unmanaged DLL (advapi32).
public void AddPrivileges(string account, string privilege)
{
IntPtr pSid = GetSIDInformation(account);
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, pSid, privileges, 1);
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
int error = Win32Sec.LsaNtStatusToWinError((int)ret);
throw new Win32Exception(error);
}
The variable values at runtime are as follows:
privilege: "SeServiceLogonRight"
account: "named"
ret: 3221225485 (STATUS_INVALID_PARAMETER)
error: 87
When caught, the message within the Win32Exception is: "The parameter is incorrect"
The code is running on Windows Web Server 2008. I can verify that the account does exist, and this code works fine on another server... I'm not sure if this could have been caused by Windows 2008 SP2. I'm thinking that I've forgotten to install something, but I can't think what...
The code is from: http://weblogs.asp.net/avnerk/archive/2007/05/10/granting-user-rights-in-c.aspx
Following the provided link through to the code at http://www.hightechtalks.com/csharp/lsa-functions-276626.html
IntPtr GetSIDInformation(string account)
{
LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1];
LSA_TRANSLATED_SID2 lts;
IntPtr tsids = IntPtr.Zero;
IntPtr tdom = IntPtr.Zero;
names[0] = InitLsaString(account);
lts.Sid = IntPtr.Zero;
Console.WriteLine("String account: {0}", names[0].Length);
int ret = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids);
if (ret != 0)
{
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret));
}
lts = (LSA_TRANSLATED_SID2) Marshal.PtrToStructure(tsids,
typeof(LSA_TRANSLATED_SID2));
Win32Sec.LsaFreeMemory(tsids);
Win32Sec.LsaFreeMemory(tdom);
return lts.Sid;
}
lts (an LSA_TRANSLATED_SID2 struct) contains a pointer that points at memory that is freed by the call to Win32Sec.LsaFreeMemory. Using the pointer after the memory is freed is bad practice and will have unpredictable results -- it might even "work".
Tweaking the code at the link by using the SecurityIdentifier class (.Net 2 and above) along a little cleanup of unneeded code avoids the memory problem.
using System;
namespace Willys.LsaSecurity
{
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using LSA_HANDLE = IntPtr;
[StructLayout(LayoutKind.Sequential)]
struct LSA_OBJECT_ATTRIBUTES
{
internal int Length;
internal IntPtr RootDirectory;
internal IntPtr ObjectName;
internal int Attributes;
internal IntPtr SecurityDescriptor;
internal IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct LSA_UNICODE_STRING
{
internal ushort Length;
internal ushort MaximumLength;
[MarshalAs(UnmanagedType.LPWStr)]
internal string Buffer;
}
sealed class Win32Sec
{
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
internal static extern uint LsaOpenPolicy(
LSA_UNICODE_STRING[] SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
int AccessMask,
out IntPtr PolicyHandle
);
[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
internal static extern uint LsaAddAccountRights(
LSA_HANDLE PolicyHandle,
IntPtr pSID,
LSA_UNICODE_STRING[] UserRights,
int CountOfRights
);
[DllImport("advapi32")]
internal static extern int LsaNtStatusToWinError(int NTSTATUS);
[DllImport("advapi32")]
internal static extern int LsaClose(IntPtr PolicyHandle);
}
sealed class Sid : IDisposable
{
public IntPtr pSid = IntPtr.Zero;
public SecurityIdentifier sid = null;
public Sid(string account)
{
sid = (SecurityIdentifier) (new NTAccount(account)).Translate(typeof(SecurityIdentifier));
Byte[] buffer = new Byte[sid.BinaryLength];
sid.GetBinaryForm(buffer, 0);
pSid = Marshal.AllocHGlobal(sid.BinaryLength);
Marshal.Copy(buffer, 0, pSid, sid.BinaryLength);
}
public void Dispose()
{
if (pSid != IntPtr.Zero)
{
Marshal.FreeHGlobal(pSid);
pSid = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~Sid()
{
Dispose();
}
}
public sealed class LsaWrapper : IDisposable
{
enum Access : int
{
POLICY_READ = 0x20006,
POLICY_ALL_ACCESS = 0x00F0FFF,
POLICY_EXECUTE = 0X20801,
POLICY_WRITE = 0X207F8
}
const uint STATUS_ACCESS_DENIED = 0xc0000022;
const uint STATUS_INSUFFICIENT_RESOURCES = 0xc000009a;
const uint STATUS_NO_MEMORY = 0xc0000017;
IntPtr lsaHandle;
public LsaWrapper()
: this(null)
{ }
// // local system if systemName is null
public LsaWrapper(string systemName)
{
LSA_OBJECT_ATTRIBUTES lsaAttr;
lsaAttr.RootDirectory = IntPtr.Zero;
lsaAttr.ObjectName = IntPtr.Zero;
lsaAttr.Attributes = 0;
lsaAttr.SecurityDescriptor = IntPtr.Zero;
lsaAttr.SecurityQualityOfService = IntPtr.Zero;
lsaAttr.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES));
lsaHandle = IntPtr.Zero;
LSA_UNICODE_STRING[] system = null;
if (systemName != null)
{
system = new LSA_UNICODE_STRING[1];
system[0] = InitLsaString(systemName);
}
uint ret = Win32Sec.LsaOpenPolicy(system, ref lsaAttr,
(int) Access.POLICY_ALL_ACCESS, out lsaHandle);
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int) ret));
}
public void AddPrivileges(string account, string privilege)
{
uint ret = 0;
using (Sid sid = new Sid(account))
{
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
ret = Win32Sec.LsaAddAccountRights(lsaHandle, sid.pSid, privileges, 1);
}
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int) ret));
}
public void Dispose()
{
if (lsaHandle != IntPtr.Zero)
{
Win32Sec.LsaClose(lsaHandle);
lsaHandle = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
~LsaWrapper()
{
Dispose();
}
// helper functions
static LSA_UNICODE_STRING InitLsaString(string s)
{
// Unicode strings max. 32KB
if (s.Length > 0x7ffe)
throw new ArgumentException("String too long");
LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
lus.Buffer = s;
lus.Length = (ushort) (s.Length * sizeof(char));
lus.MaximumLength = (ushort) (lus.Length + sizeof(char));
return lus;
}
}
}
I couldn't get this to work, so instead I used the source code from the CodeProject project, LSA Functions - Privileges and Impersonation which works nicely.
lts.Sid is freed before returning in GetSIDInformation.
Moving the codes of GetSIDInformation out. It worked fine for .Net 4.5.
public void AddPrivileges(string account, string privilege)
{
LSA_UNICODE_STRING[] names = new LSA_UNICODE_STRING[1];
LSA_TRANSLATED_SID2 lts;
IntPtr tsids = IntPtr.Zero;
IntPtr tdom = IntPtr.Zero;
names[0] = InitLsaString(account);
lts.Sid = IntPtr.Zero;
Console.WriteLine("String account: {0}", names[0].Length);
int ret1 = Win32Sec.LsaLookupNames2(lsaHandle, 0, 1, names, ref tdom, ref tsids);
if (ret1 != 0)
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError(ret1));
lts = (LSA_TRANSLATED_SID2)Marshal.PtrToStructure(tsids, typeof(LSA_TRANSLATED_SID2));
IntPtr pSid = lts.Sid;
//IntPtr pSid = GetSIDInformation(account);
LSA_UNICODE_STRING[] privileges = new LSA_UNICODE_STRING[1];
privileges[0] = InitLsaString(privilege);
uint ret = Win32Sec.LsaAddAccountRights(lsaHandle, pSid, privileges, 1);
Win32Sec.LsaFreeMemory(tsids);
Win32Sec.LsaFreeMemory(tdom);
if (ret == 0)
return;
if (ret == STATUS_ACCESS_DENIED)
{
throw new UnauthorizedAccessException();
}
if ((ret == STATUS_INSUFFICIENT_RESOURCES) || (ret == STATUS_NO_MEMORY))
{
throw new OutOfMemoryException();
}
throw new Win32Exception(Win32Sec.LsaNtStatusToWinError((int)ret));
}
I came across the same error when calling LsaAddAccountRights and I found out I was using sizeof(char) instead of sizeof(wchar) when initializing LSA_UNICODE_STRING.
I checked the code at http://www.codeproject.com/KB/cs/lsadotnet.aspx and found similar issue:
static LSA_UNICODE_STRING InitLsaString(string s)
{
// Unicode strings max. 32KB
if (s.Length > 0x7ffe)
throw new ArgumentException("String too long");
LSA_UNICODE_STRING lus = new LSA_UNICODE_STRING();
lus.Buffer = s;
lus.Length = (ushort)(s.Length * sizeof(char));
lus.MaximumLength = (ushort)(lus.Length + sizeof(char));
return lus;
}
Should be something like:
lus.Length = (ushort)(s.Length * UnicodeEncoding.CharSize);
lus.MaximumLength = (ushort)(lus.Length + UnicodeEncoding.CharSize);
I was able to get this working on one box but then on another box it failed with the error you received:
System.ComponentModel.Win32Exception: The parameter is incorrect
I discovered that the root cause of this issue for me had to do with architecture of the process that was running the code. I was running a msbuild 32-bit process which worked fine, but when I used the 64-bit msbuild.exe to run this it failed with this error.
I hope that helps!
Regards,
Brandon
I found this problem is related to .NET 4.0. Downgrade your project to .NET 3.5 and it will work.