Manipulate Afx OLE Control in External Window - powershell

I have to manipulate (including get and set text from) an accounting system using Powershell.
The process has a window which contain a grid-like container (the one with blue background colour in the screenshot).
The container contains some Text fields and Combo-boxes.
I tried different ways to get the UI controls(The Text fields/'Edit' Controls in the container).
I tried UIAutomation as follow but fail to found any Text field/'Edit' Control but found the Comobo-box.
Add-Type -AssemblyName UIAutomationClient
$ele=[System.Windows.Automation.AutomationElement]::FromHandle($hwnd)
$elecol=$ele.FindAll([System.Windows.Automation.TreeScope]::Descendants,
[System.Windows.Automation.Condition]::TrueCondition)
for ($i=0;$i -lt $elecol.count;$i++){$elecol[$i].Current} #Only found Scroll Bars & Combo
If look into the "AutomationElement.Current" of container itself ($ele.Current), it looks like the following.
ControlType : System.Windows.Automation.ControlType
LocalizedControlType : pane
Name :
AcceleratorKey :
AccessKey :
HasKeyboardFocus : False
IsKeyboardFocusable : False
IsEnabled : True
BoundingRectangle : 350,1658,2830,443
HelpText :
IsControlElement : True
IsContentElement : True
LabeledBy :
AutomationId :
ItemType :
IsPassword : False
ClassName : AfxGWOleControl42
NativeWindowHandle : 3409268
ProcessId : 11656
IsOffscreen : False
Orientation : None
FrameworkId : Win32
IsRequiredForForm : False
ItemStatus :
And if looks into the supported Patterns ($ele.GetSupportedPatterns()) of the container, it shows nothing.
I also tried using WinAPI to Enum All Child Windows but found No Text field/'Edit' Control too.
function Enum-ChildWindows{
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
[intPtr]$Hwnd
)
BEGIN{
if (("EnumChildWindowsAPI" -as [type]) -eq $null){
Add-Type #"
using System; // namespace "IntPtr"
using System.Runtime.InteropServices; // namespace "DLLImport"
using System.Collections.Generic; // namespace "List"
public class EnumChildWindowsAPI
{
[DllImport("user32")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumChildWindows(IntPtr window, EnumWindowProc callback, IntPtr i);
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc,GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
return true;
}
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
}
"#
}
}
PROCESS{
return [EnumChildWindowsAPI]::GetChildWindows($Hwnd)
}
}
And of course, sending WM_SETTEXT message to the container is also not working.
Watching into the Spy++, the class name of the grid-like container is "afxGWOleControl", shown as follow.
An interesting thing is, the combo box "window" appear in Spy++ only when it is on selected.
Is there any way to edit/read the text field inside the container?

Related

RegistryValueKind NONE

How can I set a none data type.[Microsoft.Win32.RegistryValueKind]::None in Powershell 2.0?
Powershell 2.0 has types "Unknown, String, ExpandString, Binary, DWord, MultiString, QWord".
learn.microsoft.com
Add-Type -TypeDefinition #"
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
namespace AdvapiSolution
{
public class Advapi
{
public enum HKEY : uint
{
HKEY_CLASSES_ROOT = 0x80000000,
HKEY_CURRENT_USER = 0x80000001,
HKEY_LOCAL_MACHINE = 0x80000002,
HKEY_USERS = 0x80000003,
HKEY_PERFORMANCE_DATA = 0x80000004,
HKEY_PERFORMANCE_TEXT = 0x80000050,
HKEY_PERFORMANCE_NLSTEXT = 0x80000060,
HKEY_CURRENT_CONFIG = 0x80000005
}
private enum VALUE_TYPE : uint
{
REG_NONE= 0,
REG_SZ = 1,
REG_EXPAND_SZ = 2,
REG_BINARY = 3,
REG_DWORD = 4,
REG_DWORD_LITTLE_ENDIAN = 4,
REG_DWORD_BIG_ENDIAN = 5,
REG_LINK = 6,
REG_MULTI_SZ = 7,
REG_RESOURCE_LIST = 8,
REG_FULL_RESOURCE_DESCRIPTOR = 9,
REG_RESOURCE_REQUIREMENTS_LIST = 10,
REG_QWORD_LITTLE_ENDIAN = 11
}
[DllImport("advapi32.dll", CharSet = CharSet.Auto, BestFitMapping = false)]
private static extern int RegSetKeyValueW (
HKEY hkey,
string lpSubKey,
string lpValueName,
VALUE_TYPE type,
byte[] data,
uint dataLength);
public int set_key(HKEY hkey, string subkey, string valuename){
return RegSetKeyValueW(hkey, subkey, valuename, VALUE_TYPE.REG_NONE, null, 0);
}
}
}
"#
In my case data equals null and type is REG_NONE. You can change set_key as you like with object:
$Advapiobject = New-Object 'AdvapiSolution.Advapi'
$Advapiobject.set_key('HKEY_CURRENT_USER',$yoursubkey, $yourvaluename, 'REG_NONE')
Another way is using a static method set_key.
Change set_key to public static int set_key:
[AdvapiSolution.Advapi]::set_key('HKEY_CURRENT_USER',$yoursubkey, $yourvaluename, 'REG_NONE').
If it returns 0, it's okay. If the function fails, the return value is a nonzero error code defined in Winerror.h.
System Error Codes
(As implied in your question), [Microsoft.Win32.RegistryValueKind]::None (which translates to a REG_NONE registry value) requires at least .NET Framework 4, whereas Windows PowerShell v2 is built on .NET Framework 2. (Only Windows PowerShell v3 and above are built on .NET Framework 4 and above).
Unlike C#, PowerShell does not allow you to pass the numerical value of an enum-typed parameter if that value doesn't represent an officially defined member of that enumeration (at that time, in the underlying framework), so attempting to pass (-1), the value of [Microsoft.Win32.RegistryValueKind]::None, does not work in Windows PowerShell v2 - neither with New-ItemProperty / Set-ItemProperty's -Type parameter, nor with .NET API calls ([Microsoft.Win32.Registry]::SetValue(...)).
Therefore, in Windows PowerShell v2, calling the Windows API via P/Invoke declarations implemented via Add-Type is probably your only option - see the RegSetKeyValue() WinAPI function (among others).
Building on your own helpful answer, here's a streamlined solution, which:
uses Add-Type's -MemberDefinition parameter to simplify creation of types that wrap P/Invoke calls.
Exposes a static [WinApiHelper.Registry]::SetNoneValue() method, which:
focuses only on creating REG_NONE values
also supports creating such values with byte data ([byte[]] arrays)
has no return value, but throws a Win32Exception should an error occur.
Add-Type -Namespace WinApiHelper -Name Registry -MemberDefinition #'
public enum HKEY : uint
{
HKEY_CLASSES_ROOT = 0x80000000,
HKEY_CURRENT_CONFIG = 0x80000005,
HKEY_CURRENT_USER = 0x80000001,
HKEY_LOCAL_MACHINE = 0x80000002,
HKEY_USERS = 0x80000003
}
[DllImport("advapi32.dll")]
private static extern int RegSetKeyValue(
HKEY hkey,
string lpSubKey,
string lpValueName,
UInt32 type,
byte[] data,
UInt32 dataLength
);
public static void SetNoneValue(HKEY hkey, string subkey, string valuename, byte[] bytes)
{
int rc = RegSetKeyValue(hkey, subkey, valuename, 0 /* REG_NONE */, bytes, (UInt32) (bytes == null ? 0 : bytes.Length));
if (rc != 0) {
// Access the error code in PowerShell with $Error[0].Exception.InnerException.NativeErrorCode
throw new System.ComponentModel.Win32Exception(rc);
}
}
// Overload that creates an empty value.
// Note: .NET 2 doesn't support optional parameters, so an explicit overload is required.
public static void SetNoneValue(HKEY hkey, string subkey, string valuename) {
SetNoneValue(hkey, subkey, valuename, null);
}
'#
Sample calls:
# Create an empty REG_NONE value named 'bar1' in key HKEY_CURRENT_USER\foo.
[WinApiHelper.Registry]::SetNoneValue('HKEY_CURRENT_USER', 'foo', 'bar1')
# Create a REG_NONE value named 'bar2' with a byte array.
[WinApiHelper.Registry]::SetNoneValue('HKEY_CURRENT_USER', 'foo', 'bar2', [byte[]] (42, 43))

System.Threading.Timer kills the PowerShell console

When I run this in a PowerShell console:
$callback = [System.Threading.TimerCallback]{
param($state)
}
$timer = [System.Threading.Timer]::new($callback, $null,
[timespan]::Zero,
[timespan]::FromSeconds(1))
Then once $callback gets called (I made sure that this is the root cause, by changing the dueTime constructor's parameter from [timespan]::Zero to longer delays), the whole console process crashes, saying that powershell has stopped working.
What could be the reason? how can I overcome this?
The error is:
There is no Runspace available to run scripts in this thread. You can
provide one in the DefaultRunspace property of the
System.Management.Automation.Runspaces.Runspace type.
And stems from
System.Management.Automation.ScriptBlock.GetContextFromTLS()
The delegate is the issue. This is how I got it working:
$helper = #'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Runspaces;
public class RunspacedDelegateFactory
{
public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace)
{
Action setRunspace = () => Runspace.DefaultRunspace = runspace;
return ConcatActionToDelegate(setRunspace, _delegate);
}
private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments)
{
var invokeMethod = _delegate.GetType().GetMethod("Invoke");
return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments);
}
public static Delegate ConcatActionToDelegate(Action a, Delegate d)
{
var parameters =
d.GetType().GetMethod("Invoke").GetParameters()
.Select(p => Expression.Parameter(p.ParameterType, p.Name))
.ToArray();
Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters));
var lambda = Expression.Lambda(d.GetType(), body, parameters);
var compiled = lambda.Compile();
return compiled;
}
}
'#
add-type -TypeDefinition $helper
$runspacedDelegate = [RunspacedDelegateFactory]::NewRunspacedDelegate($callback, [Runspace]::DefaultRunspace)
$timer = [System.Threading.Timer]::new(
$runspacedDelegate,
$null,
[timespan]::Zero,
[timespan]::FromSeconds(1))
I borrowed NewRunspacedDelegate from
https://www.powershellgallery.com/packages/ContainerTools/0.0.1

Powershell automated testing code freezes when pop up window appears in GUI.

I'm using Jenkins to perform some automated web testing on a web application.
I'm using powershell to automatically fill out forms, return values, etc. But sometimes a popup window will appear mid test. Weather its an error message or a sub form that needs to be filled out.
When this happens though, my code freezes indefinitely and I need to either exit out of the build or manually close the pop up window myself (defeating the purpose of automated testing).
Ideally I would like to be able to select the open pop up window as an object and fill out forms/ click buttons on it, but my code just freezes and doesn't give me chance.
Would anyone have any advice on how to overcome this?
I don't know Jenkins but here is a PowerShell function that shows a Messagebox that will disapear after a timeout interval specified in milli seconds. To this popup won't be blocking.
function Show-Messagebox
{
param([String]$Title, [String]$Message, [Int]$TimeOut=2000)
$TypeDef = #'
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
public class Win32API
{
private const UInt32 WM_CLOSE = 0x0010;
[DllImport("user32.dll", EntryPoint="FindWindow", SetLastError = true)]
private static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.Dll")]
private static extern int PostMessage(IntPtr hWnd, UInt32 msg, int wParam, int lParam);
public static void ShowMessageBox(string Message, string Caption, int TimeOut = 2000)
{
var timer = new System.Timers.Timer(TimeOut) { AutoReset = false };
timer.Elapsed += delegate
{
IntPtr hWnd = FindWindowByCaption(IntPtr.Zero, Caption);
if (hWnd.ToInt32() != 0) PostMessage(hWnd, WM_CLOSE, 0, 0);
};
timer.Enabled = true;
MessageBox.Show(Message, Caption);
}
}
'#
Add-Type -TypeDefinition $TypeDef -ReferencedAssemblies System.Windows.Forms
[Win32API]::ShowMessageBox($Message, $Title, $TimeOut)
}
The function can be called like so:
Show-Messagebox -Title "The Title" -Message "The complete message"
It's a little clumsy having to put the whole type definition of a class in C# code as a here string into the function (there a no white spaces allowed before the end mark '# of the here string) but it's still a simple copy&paste solution.
If you replace the [System.Windows.Forms.MessageBox]::Show()-calls in a PoweShell script with this function there should be no blocking Messageboxes any more.

Setting screen resolution on Windows 10

I'm trying to set the screen resolution via script (Powershell).
I found the cmdlet Set-DisplayResolution, which should work on Server 2012/R2 and 8/8.1 (where it won't work), and it also didn't work on Windows 10 PS 5.0.
I also had a look at
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Screen]::AllScreens
and
[System.Windows.Forms.Screen]::PrimaryScreen
but it showed weird output:
BitsPerPixel : 32
Bounds : {X=0,Y=0,Width=2000,Height=1333}
DeviceName : \\.\DISPLAY1
Primary : True
WorkingArea : {X=0,Y=0,Width=2000,Height=1293}
I am running a 3000x2000 resolution. Even after I changed my display to 1920x1080 the output stays the same.
After a longer search I found this script, which does the job using win32 API. Is there a more handy way for doing this, as this Set-DisplayResolution just running on Windows 10 Installation?
I also would like to set the UI scaling of Windows 10 (like 150%).
And for sure, i would like to know, why the AllScreens and PrimaryScreen methods of [System.Windows.Forms.Screen] aren't functioning properly.
Refer below script to change display resolution on Windows 10. PowerShell server core does not supported on windows 10. You can use below script
Important : you can provide display resolution at end of the script
currently it is set as Set-ScreenResolution -Width 1920 -Height 1080. You can change these values. Copy this complete script and save as Set-ScreenResolution.ps1 and execute using PowerShell. Make sure you Run powershell as admin
Function Set-ScreenResolution {
<#
.Synopsis
Sets the Screen Resolution of the primary monitor
.Description
Uses Pinvoke and ChangeDisplaySettings Win32API to make the change
.Example
Set-ScreenResolution -Width 1024 -Height 768
#>
param (
[Parameter(Mandatory=$true,
Position = 0)]
[int]
$Width,
[Parameter(Mandatory=$true,
Position = 1)]
[int]
$Height
)
$pinvokeCode = #"
using System;
using System.Runtime.InteropServices;
namespace Resolution
{
[StructLayout(LayoutKind.Sequential)]
public struct DEVMODE1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public short dmOrientation;
public short dmPaperSize;
public short dmPaperLength;
public short dmPaperWidth;
public short dmScale;
public short dmCopies;
public short dmDefaultSource;
public short dmPrintQuality;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string dmFormName;
public short dmLogPixels;
public short dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
};
class User_32
{
[DllImport("user32.dll")]
public static extern int EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE1 devMode);
[DllImport("user32.dll")]
public static extern int ChangeDisplaySettings(ref DEVMODE1 devMode, int flags);
public const int ENUM_CURRENT_SETTINGS = -1;
public const int CDS_UPDATEREGISTRY = 0x01;
public const int CDS_TEST = 0x02;
public const int DISP_CHANGE_SUCCESSFUL = 0;
public const int DISP_CHANGE_RESTART = 1;
public const int DISP_CHANGE_FAILED = -1;
}
public class PrmaryScreenResolution
{
static public string ChangeResolution(int width, int height)
{
DEVMODE1 dm = GetDevMode1();
if (0 != User_32.EnumDisplaySettings(null, User_32.ENUM_CURRENT_SETTINGS, ref dm))
{
dm.dmPelsWidth = width;
dm.dmPelsHeight = height;
int iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_TEST);
if (iRet == User_32.DISP_CHANGE_FAILED)
{
return "Unable To Process Your Request. Sorry For This Inconvenience.";
}
else
{
iRet = User_32.ChangeDisplaySettings(ref dm, User_32.CDS_UPDATEREGISTRY);
switch (iRet)
{
case User_32.DISP_CHANGE_SUCCESSFUL:
{
return "Success";
}
case User_32.DISP_CHANGE_RESTART:
{
return "You Need To Reboot For The Change To Happen.\n If You Feel Any Problem After Rebooting Your Machine\nThen Try To Change Resolution In Safe Mode.";
}
default:
{
return "Failed To Change The Resolution";
}
}
}
}
else
{
return "Failed To Change The Resolution.";
}
}
private static DEVMODE1 GetDevMode1()
{
DEVMODE1 dm = new DEVMODE1();
dm.dmDeviceName = new String(new char[32]);
dm.dmFormName = new String(new char[32]);
dm.dmSize = (short)Marshal.SizeOf(dm);
return dm;
}
}
}
"#
Add-Type $pinvokeCode -ErrorAction SilentlyContinue
[Resolution.PrmaryScreenResolution]::ChangeResolution($width,$height)
}
Set-ScreenResolution -Width 1920 -Height 1080
Sorry to tell, but it's not possible with powershell:
[System.Windows.Forms.Screen] - the namespace says all you need to understand: This namespace refers to the display area for the application you've coded as an windows form (Usefull, if you have to change the windows-size within your application)
The Cmdlet Set-Displayresolution only works on Windows Server 2016 Core (or any other Core-Server in the future). It is documented on Technet : https://technet.microsoft.com/de-de/library/jj603036(v=wps.630).aspx
The workaround to use Win32-API, that you found earlier, looks like the best solution you could get.
A bit late, but depending on how you need the resolution set you could use AHK (scripting language). This would require a script opening the display settings dialog and sending clicks or keystrokes to it. If you're interested I can give more information
Update:
First, you have to have AutoHotKey installed on your computer (this is straightforward enough to just Google). Next, use a script to send keyboard input to the control panel display settings. It might look something like this:
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#SingleInstance, force
detecthiddenwindows, on
; Use command prompt to open display settings
Run, %comspec% \k,,hide, cmd_pid
WinWait, ahk_pid %cmd_pid%
ControlSend, , control desk.cpl`n, ahk_pid %cmd_pid%
; Wait for display settings to open
WinWaitActive, Settings
sleep, 2000
; Send the appropriate key strokes to the settings menu to change the resolution
Send,{Tab 3}
sleep 100
Send,{Enter}
sleep 100
; Change this as needed to move to the desired display settings
Send,{Down 2}
sleep 100
Send,{Enter}
return

Powershell Service Account Password Change Logon Failure

i am trying to use a powershell script that allows me to change the user account and password a specific service runs under.
$account="domain\account"
$password="password"
$svc=gwmi win32_service -filter "name='MyService'"
$svc.change($null,$null,$null,$null,$null,$false,$account,$password,$null,$null,$null)
I could check that the account was changed but when i tried to run the service from the Service.msc GUI it fails with a logon failure.
If I use to start the service from the script itself i get the following error.
$svc.StartService()
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0
PSComputerName :
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 15
PSComputerName :
According to microsoft it is a logon error : Return Value 15.
http://msdn.microsoft.com/en-us/library/windows/desktop/aa384901%28v=vs.85%29.aspx
I have verified the password a millions time and when I copy and paste the password using the GUI it just works.
May be i am missing something in my script. (SecurityPolicy is unrestricted on this machine)
You need to set the SeServiceLogonRight privilege on the user before setting the service account.
Set-Privileges $account "SeServiceLogonRight"
$svc=gwmi win32_service -filter "name='MyService'"
$svc.change($null,$null,$null,$null,$null,$false,$account,$password,$null,$null,$null)
An example of the Set-Privileges function which uses LSA:
function Set-Privileges
{
param(
$username,
$Privilege
)
# C# code from http://www.codeproject.com/Articles/4863/LSA-Functions-Privileges-and-Impersonation
$Source = #"
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace Privileges {
public class LsaUtility {
// Import the LSA functions
[DllImport("advapi32.dll", PreserveSig=true)]
private static extern UInt32 LsaOpenPolicy(
ref LSA_UNICODE_STRING SystemName,
ref LSA_OBJECT_ATTRIBUTES ObjectAttributes,
Int32 DesiredAccess,
out IntPtr PolicyHandle
);
[DllImport("advapi32.dll", SetLastError=true, PreserveSig=true)]
private static extern int LsaAddAccountRights(
IntPtr PolicyHandle,
IntPtr AccountSid,
LSA_UNICODE_STRING[] UserRights,
int CountOfRights);
[DllImport("advapi32")]
public static extern void FreeSid(IntPtr pSid);
[DllImport( "advapi32.dll", CharSet=CharSet.Auto, SetLastError=true, PreserveSig=true)]
private static extern bool LookupAccountName(
string lpSystemName, string lpAccountName,
IntPtr psid,
ref int cbsid,
StringBuilder domainName, ref int cbdomainLength, ref int use );
[DllImport( "advapi32.dll")]
private static extern bool IsValidSid(IntPtr pSid);
[DllImport("advapi32.dll")]
private static extern int LsaClose(IntPtr ObjectHandle);
[DllImport("kernel32.dll")]
private static extern int GetLastError();
[DllImport("advapi32.dll")]
private static extern int LsaNtStatusToWinError(int status);
// define the structures
[StructLayout(LayoutKind.Sequential)]
private struct LSA_UNICODE_STRING {
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
}
[StructLayout(LayoutKind.Sequential)]
private struct LSA_OBJECT_ATTRIBUTES{
public int Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING ObjectName;
public UInt32 Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;}
// enum all policies
private enum LSA_AccessPolicy : long{
POLICY_VIEW_LOCAL_INFORMATION = 0x00000001L,
POLICY_VIEW_AUDIT_INFORMATION = 0x00000002L,
POLICY_GET_PRIVATE_INFORMATION = 0x00000004L,
POLICY_TRUST_ADMIN = 0x00000008L,
POLICY_CREATE_ACCOUNT = 0x00000010L,
POLICY_CREATE_SECRET = 0x00000020L,
POLICY_CREATE_PRIVILEGE = 0x00000040L,
POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x00000080L,
POLICY_SET_AUDIT_REQUIREMENTS = 0x00000100L,
POLICY_AUDIT_LOG_ADMIN = 0x00000200L,
POLICY_SERVER_ADMIN = 0x00000400L,
POLICY_LOOKUP_NAMES = 0x00000800L,
POLICY_NOTIFICATION = 0x00001000L
}
/// <summary>Adds a privilege to an account</summary>
/// <param name="accountName">Name of an account - "domain\account" or only "account"</param>
/// <param name="privilegeName">Name ofthe privilege</param>
/// <returns>The windows error code returned by LsaAddAccountRights</returns>
public static int SetRight(String accountName, String privilegeName){
int winErrorCode = 0; //contains the last error
//pointer an size for the SID
IntPtr sid = IntPtr.Zero;
int sidSize = 0;
//StringBuilder and size for the domain name
StringBuilder domainName = new StringBuilder();
int nameSize = 0;
//account-type variable for lookup
int accountType = 0;
//get required buffer size
LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
//allocate buffers
domainName = new StringBuilder(nameSize);
sid = Marshal.AllocHGlobal(sidSize);
//lookup the SID for the account
bool result = LookupAccountName(String.Empty, accountName, sid, ref sidSize, domainName, ref nameSize, ref accountType);
//say what you're doing for debug
//Console.WriteLine("LookupAccountName result = "+result);
//Console.WriteLine("IsValidSid: "+IsValidSid(sid));
//Console.WriteLine("LookupAccountName domainName: "+domainName.ToString());
if( ! result ){
winErrorCode = GetLastError();
Console.WriteLine("LookupAccountName failed: "+ winErrorCode);
}else{
//initialize an empty unicode-string
LSA_UNICODE_STRING systemName = new LSA_UNICODE_STRING();
//combine all policies
int access = (int)(
LSA_AccessPolicy.POLICY_AUDIT_LOG_ADMIN |
LSA_AccessPolicy.POLICY_CREATE_ACCOUNT |
LSA_AccessPolicy.POLICY_CREATE_PRIVILEGE |
LSA_AccessPolicy.POLICY_CREATE_SECRET |
LSA_AccessPolicy.POLICY_GET_PRIVATE_INFORMATION |
LSA_AccessPolicy.POLICY_LOOKUP_NAMES |
LSA_AccessPolicy.POLICY_NOTIFICATION |
LSA_AccessPolicy.POLICY_SERVER_ADMIN |
LSA_AccessPolicy.POLICY_SET_AUDIT_REQUIREMENTS |
LSA_AccessPolicy.POLICY_SET_DEFAULT_QUOTA_LIMITS |
LSA_AccessPolicy.POLICY_TRUST_ADMIN |
LSA_AccessPolicy.POLICY_VIEW_AUDIT_INFORMATION |
LSA_AccessPolicy.POLICY_VIEW_LOCAL_INFORMATION
);
//initialize a pointer for the policy handle
IntPtr policyHandle = IntPtr.Zero;
//these attributes are not used, but LsaOpenPolicy wants them to exists
LSA_OBJECT_ATTRIBUTES ObjectAttributes = new LSA_OBJECT_ATTRIBUTES();
ObjectAttributes.Length = 0;
ObjectAttributes.RootDirectory = IntPtr.Zero;
ObjectAttributes.Attributes = 0;
ObjectAttributes.SecurityDescriptor = IntPtr.Zero;
ObjectAttributes.SecurityQualityOfService = IntPtr.Zero;
//get a policy handle
int resultPolicy = (int)LsaOpenPolicy(ref systemName, ref ObjectAttributes, access, out policyHandle);
winErrorCode = LsaNtStatusToWinError(resultPolicy);
if(winErrorCode != 0){
Console.WriteLine("OpenPolicy failed: "+ winErrorCode);
}else{
//Now that we have the SID an the policy,
//we can add rights to the account.
//initialize an unicode-string for the privilege name
LSA_UNICODE_STRING[] userRights = new LSA_UNICODE_STRING[1];
userRights[0] = new LSA_UNICODE_STRING();
userRights[0].Buffer = Marshal.StringToHGlobalUni(privilegeName);
userRights[0].Length = (UInt16)( privilegeName.Length * UnicodeEncoding.CharSize );
userRights[0].MaximumLength = (UInt16)( (privilegeName.Length+1) * UnicodeEncoding.CharSize );
//add the right to the account
int res = LsaAddAccountRights(policyHandle, sid, userRights, 1);
winErrorCode = LsaNtStatusToWinError(res);
if(winErrorCode != 0){
Console.WriteLine("LsaAddAccountRights failed: "+ winErrorCode);
}else{
Console.WriteLine("LsaAddAccountRights successful");
}
LsaClose(policyHandle);
}
FreeSid(sid);
}
return winErrorCode;
}
}
}
"#
Add-Type -TypeDefinition $Source -Language CSharp
[Privileges.LsaUtility]::SetRight($username, $Privilege) | Out-Null
}
Not entirely relevent to this thread but could be useful to someone searching for this type of script.
PowerShell script to scan a computer/server find a service running under a specific account and stop the service, change the password, and then restrart the service.
I used the ListServices.psm1 module, http://gallery.technet.microsoft.com/scriptcenter/How-to-Clear-Printing-21d59516, to make the task easier for me. One quick note which took me a good 15 minutes to troubleshoot, it appears that if one is trying to set a password with a dollar symbol ($) in it, contain it within 'single quotes' not "double quotes"
Import-Module "C:\Tools\ListServices.psm1"
$pass = 'H:5Th7$!pc'
$Username = "abcservice"
$compName = gc env:computername
$newpass = [ADSI]"WinNT://$compName/$Username,user"
$newpass.SetPassword($pass)
$newpass.SetInfo()
Get-OSCServiceList -ComputerName "$compName" -UserName "$Username" | select Name | foreach { $_.Name} | Out-file -FilePath "C:\servicelist\service.txt"
foreach ($name in (Get-Content -Path "C:\servicelist\service.txt")) {
Write-Host "$name"
Stop-Service "$name"
Set-OSCServicePSW -ComputerName "$compName" -ServiceName $name -UserName ".\$Username" - NewPassWord $pass
Start-Service "$name"
}
HTH someone out there.
If you are just updating the password for the service account, and not changing which account runs the service, you might have more luck just changing the password itself. I've had success with this call to Win32_Service.Change():
$service.Change($Null,$Null,$Null,$Null,$Null,$Null,$Null,$Password)
This has consistently worked for me to update passwords.
Have you tried stopping the service before changing the password?
The following is working for me
$account="domain\account"
$password="password"
$svc=gwmi win32_service -filter "name='MyService'"
$svc.StopService();
$result = $svc.change($null,$null,$null,$null,$null,$false,$account,$password,$null,$null,$null)
if ($result.ReturnValue -eq '0') {write-host "Password changed"} else {write-host "Error: $result.ReturnValue"};
$svc.StartService();
I suspect that your password probably has at least one character that has special meaning in an interpolated string. Try single-quoting:
$password='password'
In general, it's a better habit to use single quotes by default, and only use double quotes when you specifically want to interpolate something.
BTW, also verify that you're using the correct service name. You need to use the Name property not the DisplayName. Try gwmi win32_service -filter "name='MyService'" at the prompt and make sure that doesn't return an error. It's probably the single vs. double quoting issue, though.
I know this issue was posted several years ago, however, I just encountered the same exact issue, with nearly identical code, resulting in the exact same returns.
The installer that I am using utilizes subinacl.exe to set permissions. The following article explains how to set those permissions:
http://www.waynezim.com/2010/02/how-to-set-permission-on-a-service-using-subinacl/
I simply added the 'i' switch in installation, and added the user to the appropriate group (if applicable). Considering the age of the article, I hope someone finds this useful.