Change some windows username programmatically (Rename windows user) - c#

How to change windows user name programmatically (using some API or command line tool)
Or how to rename a windows user?

I have written some small method to rename a windows user using System.DirectoryServices.DirectoryEntry class.
public bool RenameUser(string oldLoginName, string newLoginName)
{
bool renamed = false;
try
{
using (DirectoryEntry AD = new
DirectoryEntry("WinNT://" + Environment.MachineName + ",computer"))
{
try
{
using (DirectoryEntry NewUser = AD.Children.Find(oldLoginName, "user"))
{
if (NewUser != null)
{
NewUser.Rename(newLoginName);
renamed = true;
}
}
}
catch (Exception ex)
{
//TODO: Log
}
}
}
catch (Exception ex)
{
//TODO: Log
}
return renamed;
}

You can change the username of a user account with the NetUserSetInfo function.
If you only want to change the username set the level argument to 0 and pass a USER_INFO_0 structure. You can use a different level if you want to change several things at once.
This is a simple bit of code I've used successfully to change usernames:
#include <Windows.h>
#include <LM.h>
#include <stdio.h>
#pragma comment(lib, "netapi32.lib")
int main(int argc, char ** argv)
{
USER_INFO_0 ui0;
NET_API_STATUS result;
LPWSTR command = GetCommandLineW();
wchar_t newname[21];
while (*command != L'*') command++;
command++;
ui0.usri0_name = newname;
wcscpy_s(newname, _countof(newname), L"decommiss-");
wcscat_s(newname, _countof(newname), command);
result = NetUserSetInfo(NULL, command, 0, (LPBYTE)&ui0, NULL);
printf("%u\n", result);
return result;
}

You can not change obviously the name of the user on Windows system, as it kind of key for a lot of internal resources, but you can change DisplayName of it, which, by the way, will not affect on internal File structure, so kind of cosmetic change. Which most probably will create confusion for you, or for other users on the same machine along years of use, so I would suggest do not do that. But if you want, here is powershell script example, that should work for you :
$CurrentUserName = "Your_Domain_Name/Current_User_Name"
Get-QADUser -SearchRoot $CurrentUserName `
| Set-QADUser -DisplayName "New_User_Name" `
| FT FirstName, LastName, DisplayName, company
For more detailed description look on this good example:
Change user DisplayName from powershell
Note that here they use extraplugin for PowerShell.
EDIT
another link on subject to clarify what I mean :
Change user name on Windows7 Professional
Hope this helps.

Related

c# service: how to get user profile folder path

I need to get the user directory from within a C# windows service...
...like C:\Users\myusername\
Ideally, I'd like to have the roaming path...
...like C:\Users\myusername\AppData\Roaming\
When I used the following in a console program I got the correct user directory...
System.Environment.GetEnvironmentVariable("USERPROFILE");
...but when I use that same variable in a service, I get...
C:\WINDOWS\system32\config\systemprofile
How can I get the user folder and maybe even the roaming folder location from a service?
Thanks in advance.
I have searched for getting the profile path of user from Windows service. I have found this question, which does not include a way to do it. As I have found the solution, partly based on a comment by Xavier J on his answer, I have decided to post it here for others.
Following is a piece of code to do that. I have tested it on few systems, and it should work on different OSes ranging from Windows XP to Windows 10 1903.
//You can either provide User name or SID
public string GetUserProfilePath(string userName, string userSID = null)
{
try
{
if (userSID == null)
{
userSID = GetUserSID(userName);
}
var keyPath = #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" + userSID;
var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyPath);
if (key == null)
{
//handle error
return null;
}
var profilePath = key.GetValue("ProfileImagePath") as string;
return profilePath;
}
catch
{
//handle exception
return null;
}
}
public string GetUserSID(string userName)
{
try
{
NTAccount f = new NTAccount(userName);
SecurityIdentifier s = (SecurityIdentifier)f.Translate(typeof(SecurityIdentifier));
return s.ToString();
}
catch
{
return null;
}
}
First, you'll want to use Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)
Environment.SpecialFolder.ApplicationData is for roaming profiles.
Find all SpecialFolder enumeration values here: https://msdn.microsoft.com/en-us/library/system.environment.specialfolder(v=vs.110).aspx
As others have noted, the Service will run under the account LocalSystem/LocalService/NetworkService, depending on configuration: https://msdn.microsoft.com/en-us/library/windows/desktop/ms686005(v=vs.85).aspx
A service doesn't log on like a user, unless the service is configured to use a specific user's profile. So it's not going to point to "user" folders.

How to Create a classes for 2 different target platforms

I have 2 projects- one that is a web service and the other is a windows mobile application that queries that webservice.
Say for instance I have a class called 'Animal'. I want to use the webservice to return an instance of an Animal to the mobile device. The problem being that the mobile device obviously doesnt support the full .net framework, and the class Animal has some features that require the full framework.
What are my best options here? The class Animal will really only contain properties that are just text. Am I best parsing the data into an XML message and sending this back to the mobile device (so not actually using the Animal object on the mobile device?) or do I create 2 classes, one for each platform?
Thanks
You can share a code file between two projects. Right click your project, choose Add -> Existing item, and then click the down arrow next to the Add button, you will see a "Add as Link" option there, if you know you class will compile for both projects even if they target different platforms, you can use this to share the class for both projects
I run Windows and Mobile using the same code.
Mobile devices have the word PocketPC defined in the Project's Properties, therefore, all you have to do (since you are coding in C#) is:
public static bool CreateDirectoryWithPermission(string path) {
bool ok = false;
DirectoryInfo dir = new DirectoryInfo(path);
#if !PocketPC
try {
DirectorySecurity ds;
if (dir.Exists) {
ds = dir.GetAccessControl();
} else {
ds = dir.Parent.GetAccessControl();
}
string user = Environment.UserDomainName + #"\" + Environment.UserName;
FileSystemAccessRule rule = new FileSystemAccessRule(user, FileSystemRights.FullControl, AccessControlType.Allow);
ds.AddAccessRule(rule);
dir.Create(ds);
ok = true;
} catch (Exception) { }
#endif
if (!ok) {
try {
dir.Create();
ok = true;
} catch (Exception) { }
}
return ok;
}
If I remember correctly, System.Security.AccessControl is not defined under Windows Mobile, so the DirectorySecurity is undefined.
UPDATE:
Here is another way to do what you are interested in: Create a Serializable class in a completely separate namespace, use that namespace in both projects, and pass the serialized data from the Webservice to the Mobile device. I do that, as well, but there is more code.
namespace LocksAnimal {
[Serializable()]
public class Animal {
private string name;
public Animal() {
name = "Lock";
}
public string GetName() {
#ifdef PocketPC
return name + " (Mobile Version)";
#else
return name + " (Webservice Version)";
#endif
}
}
}
The Webservice version, of course, can access more detailed information (like GetAccessControl() shown in the first code segment).
I hope this gives you some ideas.

hijack program’s command to run notepad

I have a utility programs’s EXE file, when i run this file there is a winform only and there is button when we click on it, it run windows’s notepad. Now I want to hijack this program’s command to run notepad and instead of running notepad I want to run MS Word. I know C# and VB.NET. What I need to do this ?
You can try to add in folder with this program your own program called notepad.exe that should do only one thing: run word.
If you want to do it programatically in C then you should read this page - maybe it helps: Intercepted: Windows Hacking via DLL Redirection
You can use a trick to replace programs with another by making changes to the registry. This will work even if the program you are running uses absolute paths to run notepad. It overrides any instance of the running program with the chosen one no matter where it resides. And you won't have to patch the file. The key you'd be interested in is:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
Add a key with the name of the program and add a Debugger string with the path to the program you want to replace it with. Of course you need to have permissions to make the necessary modifications. This page explains how you can replace Windows Notepad with another program. You can apply the same process here.
Though you'll probably not want to have this permanent change, so you can write up a program to temporarily add/change the key, run your program then change it back. Here's a complete one I just whipped up to temporarily replace Notepad with Word for a demonstration. Seems to work perfectly fine (though as always, use at your own risk). Just make all the necessary changes to fit your situation.
using System.Diagnostics;
using Microsoft.Win32;
namespace ProgramLauncher
{
class Program
{
// change the following constants as needed
const string PROGRAM_NAME = #"notepad.exe";
const string REPLACEMENT_PATH = #"C:\Program Files (x86)\Microsoft Office\Office12\WINWORD.EXE";
const string RUNNING_PATH = #"C:\Windows\notepad.exe";
// root key
const string KEY = #"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options";
static void Main(string[] args)
{
using (var rootKey = Registry.LocalMachine.OpenSubKey(KEY, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
var oldPath = default(string);
var needsRestoration = false;
try
{
oldPath = BackupKey(rootKey, PROGRAM_NAME, REPLACEMENT_PATH);
needsRestoration = true;
Process.Start(RUNNING_PATH).WaitForExit();
}
finally
{
if (needsRestoration)
RestoreKey(rootKey, PROGRAM_NAME, oldPath);
}
}
}
static string BackupKey(RegistryKey rootKey, string programName, string newPath)
{
Debug.Assert(rootKey != null);
Debug.Assert(!string.IsNullOrEmpty(programName));
Debug.Assert(!string.IsNullOrEmpty(newPath) && System.IO.File.Exists(newPath));
if (newPath.Contains(" "))
newPath = string.Format("\"{0}\"", newPath);
using (var programKey = rootKey.CreateSubKey(programName, RegistryKeyPermissionCheck.ReadWriteSubTree))
{
var oldDebugger = programKey.GetValue("Debugger") as string;
programKey.SetValue("Debugger", newPath, RegistryValueKind.String);
return oldDebugger;
}
}
static void RestoreKey(RegistryKey rootKey, string programName, string oldPath)
{
Debug.Assert(rootKey != null);
Debug.Assert(!string.IsNullOrEmpty(programName));
if (oldPath != null)
{
using (var programKey = rootKey.OpenSubKey(programName, RegistryKeyPermissionCheck.ReadWriteSubTree))
programKey.SetValue("Debugger", oldPath);
}
else
{
rootKey.DeleteSubKey(programName);
}
}
}
}

Create Active Directory user in .NET (C#)

I need to create a new user in Active Directory. I have found several examples like the following:
using System;
using System.DirectoryServices;
namespace test {
class Program {
static void Main(string[] args) {
try {
string path = "LDAP://OU=x,DC=y,DC=com";
string username = "johndoe";
using (DirectoryEntry ou = new DirectoryEntry(path)) {
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Add(username);
ou.CommitChanges();
}
}
catch (Exception exc) {
Console.WriteLine(exc.Message);
}
}
}
}
When I run this code I get no errors, but no new user is created.
The account I'm running the test with has sufficient privileges to create a user in the target Organizational Unit.
Am I missing something (possibly some required attribute of the user object)?
Any ideas why the code does not give exceptions?
EDIT
The following worked for me:
int NORMAL_ACCOUNT = 0x200;
int PWD_NOTREQD = 0x20;
DirectoryEntry user = ou.Children.Add("CN=" + username, "user");
user.Properties["sAMAccountName"].Value = username;
user.Properties["userAccountControl"].Value = NORMAL_ACCOUNT | PWD_NOTREQD;
user.CommitChanges();
So there were actually a couple of problems:
CommitChanges must be called on user (thanks Rob)
The password policy was preventing the user to be created (thanks Marc)
I think you are calling CommitChanges on the wrong DirectoryEntry. In the MSDN documentation (http://msdn.microsoft.com/en-us/library/system.directoryservices.directoryentries.add.aspx) it states the following (emphasis added by me)
You must call the CommitChanges method on the new entry to make the creation permanent. When you call this method, you can then set mandatory property values on the new entry. The providers each have different requirements for properties that need to be set before a call to the CommitChanges method is made. If those requirements are not met, the provider might throw an exception. Check with your provider to determine which properties must be set before committing changes.
So if you change your code to user.CommitChanges() it should work, if you need to set more properties than just the account name then you should get an exception.
Since you're currently calling CommitChanges() on the OU which hasn't been altered there will be no exceptions.
Assuming your OU path OU=x,DC=y,DC=com really exists - it should work :-)
Things to check:
you're adding a value to the "samAccountName" - why don't you just set its value:
user.Properties["sAMAccountName"].Value = username;
Otherwise you might end up with several samAccountNames - and that won't work.....
you're not setting the userAccountControl property to anything - try using:
user.Properties["userAccountControl"].Value = 512; // normal account
do you have multiple domain controllers in your org? If you, and you're using this "server-less" binding (not specifying any server in the LDAP path), you could be surprised where the user gets created :-) and it'll take several minutes up to half an hour to synchronize across the whole network
do you have a strict password policy in place? Maybe that's the problem. I recall we used to have to create the user with the "doesn't require password" option first, do a first .CommitChanges(), then create a powerful enough password, set it on the user, and remove that user option.
Marc
Check the below code
DirectoryEntry ouEntry = new DirectoryEntry("LDAP://OU=TestOU,DC=TestDomain,DC=local");
for (int i = 3; i < 6; i++)
{
try
{
DirectoryEntry childEntry = ouEntry.Children.Add("CN=TestUser" + i, "user");
childEntry.CommitChanges();
ouEntry.CommitChanges();
childEntry.Invoke("SetPassword", new object[] { "password" });
childEntry.CommitChanges();
}
catch (Exception ex)
{
}
}

How to programatically find out the last login time to a machine?

I would like to a) programatically and b) remotely find out the last date/time that a user successfully logged into a Windows machine (via remote desktop or at the console). I would be willing to take any typical windows language (C, C#, VB, batch files, JScript, etc...) but any solution would be nice.
Try this:
public static DateTime? GetLastLogin(string domainName,string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Domain,domainName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You will need to add references to using using System.DirectoryServices and
System.DirectoryServices.AccountManagement
EDIT: You might be able to get the last login Datetime to a specific machine by doing something like this:
public static DateTime? GetLastLoginToMachine(string machineName, string userName)
{
PrincipalContext c = new PrincipalContext(ContextType.Machine, machineName);
UserPrincipal uc = UserPrincipal.FindByIdentity(c, userName);
return uc.LastLogon;
}
You can use DirectoryServices to do this in C#:
using System.DirectoryServices;
DirectoryEntry dirs = new DirectoryEntry("WinNT://" + Environment.MachineName);
foreach (DirectoryEntry de in dirs.Children)
{
if (de.SchemaClassName == "User")
{
Console.WriteLine(de.Name);
if (de.Properties["lastlogin"].Value != null)
{
Console.WriteLine(de.Properties["lastlogin"].Value.ToString());
}
if (de.Properties["lastlogoff"].Value != null)
{
Console.WriteLine(de.Properties["lastlogoff"].Value.ToString());
}
}
}
After much research, I managed to put together a nice PowerShell script which takes in a computer name and lists out the accounts and the ACTUAL last logon on the specific computer. ... TURNS OUT MICROSOFT NEVER PROGRAMMED THIS FUNCTION CORRECTLY, IT TRIES TO GET LOGON INFORMATION FROM ACTIVE DIRECTORY AND STORE IT LOCALLY, SO YOU HAVE USERS LOG INTO OTHER MACHINES AND IT'LL UPDATE THE LOGIN DATES ON YOUR MACHINE, BUT THEY NEVER LOGGED INTO YOUR MACHINE.... COMPLETE FAILURE!
$REMOTEMACHINE = "LoCaLhOsT"
$NetLogs = Get-WmiObject Win32_NetworkLoginProfile -ComputerName $REMOTEMACHINE
foreach ($NetLog in $NetLogs)
{
if($NetLog.LastLogon)
{
$LastLogon = [Management.ManagementDateTimeConverter]::ToDateTime($NetLog.LastLogon)
if($LastLogon -ne [DateTime]::MinValue)
{
Write-Host $NetLog.Name ' - ' $LastLogon
}
}
}
Note: From what I can gather, the Win32_NetworkLoginProfile is using the "Win32API|Network Management Structures|USER_INFO_3" structure. So the C# equivalent would be to pInvoke [NetUserEnum] with the level specifying the structures returned in the buffer if you wanted to avoid using WMI.
Fun Fact: The DateTime is returned as an Int32 through USER_INFO_3 structure, so when it's the year 2038, the date will no longer be correct once integer overflow occurs.... #UnixMillenniumBug
You could also use CMD:
C:\Windows\System32>query user /server:<yourHostName>
OR (shorter)
C:\Windows\System32>quser /server:<yourHostName>
Output will be something like this:
USERNAME SESSIONNAME ID STATE LOGONTIME
userXYZ console 1 Active 19.01.2017 08:59
I ended up using pInvoke for "RegQueryInfoKey" in C# to retrieve LastWriteTime on the registry key of each profile located under "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
https://stackoverflow.com/a/45176943/7354452

Categories

Resources