How to remotely delete an AD-Computer from Active Directory - Powershell - powershell

I am running Powershell on a remote computer that is not connected to the domain, does not have any modules and is running PS 2.0.
I want to contact the Active Directory of my domain, check if there is an entry for this computer and; if yes, delete that entry.
Checking the AD via ADSI for existance of the computer is easy. However the deleting does not work somehow.
Here is my code so far:
# Variables
$domain = "Test.com"
$Ldap = "LDAP://$domain"
$Global:AdsiSearcher = $Null
# Function to Delete PC
Function DeleteThisPc ()
{
$CurrentSearch = $Global:AdsiSearcher
$One = $CurrentSearch.FindOne()
$OPath = [adsi]$One.Path
$OPath.psbase.DeleteTree()
The Problem lies here. Even though $OPath is of type System.DirectoryServices.DirectoryEntry and the propertylist shows all properties, it does not allow me to delete the object.
Exception calling "DeleteTree" with "0" argument(s): "Logon failure:
unknown user name or bad password.
At C:\TEMP\Domjoin1.1.ps1:49 char:33 $OPath.psbase.DeleteTree <<<< ()
CategoryInfo: NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : DotNetMethodException
Code:
# Function to get a ADSISearcher and set it to the global-AdsiSearcher
Function ConnectAD ()
{
$domain = new-object DirectoryServices.DirectoryEntry($Ldap,"$domain\Bob",'1234')
$filter = "(&(objectCategory=computer)(objectClass=computer)(cn=$ComputerName))"
$AdsiSearch = [adsisearcher]""
$AdsiSearch.SearchRoot = $domain
$AdsiSearch.Filter = $filter
$Global:AdsiSearcher = $AdsiSearch
}
# Main Function
Function Sub_Check-ADComputer()
{
ConnectAD
$CurSearch = $Global:AdsiSearcher.findOne()
if($CurSearch -ne $null)
{
DeleteThisPc
}
}
# Start
Sub_Check-ADComputer
Even though the issue seems to be obvious as the error states:
Logon failure: unknown user name or bad password.
The username and password is the same that I use to get the object from the AD in the first place. So it does work - do I somehow have to give the credentials again when trying to deleteTree() ? I also gave the User FullControl on the OU that the object is stored in.
Edit:
When I do it on another machine with PS 3.0 I get a different Error message:
Exception calling "DeleteTree" with "0" argument(s): "Access is
denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))"

I found the problem.
When using invoke command the variables are not transmitted unless specified by -argumentlist. Another approach I discovered was the following, which is the one I am using now and which works like a charm.
$domain = "DOMAINNAME"
$AdUser = "$domain\JoinDom"
$AdPW = "PASSWORD"
$AdPass = convertto-securestring -string $AdPW -AsPlainText -Force
$AdCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $AdUser,$AdPass
$ThisComputer = $Env:COMPUTERNAME
$RetValue = $true
Function CheckExist ()
{
$ErrorActionPreference = ‘SilentlyContinue’
$Ascriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("get-adcomputer $ThisComputer")
$Ret = Invoke-Command -ComputerName SERVERNAME -ScriptBlock $Ascriptblock -Credential $AdCred
$ErrorActionPreference = ‘Continue’
return $Ret
}
$ExistBefore = CheckExist
if($ExistBefore -ne $null)
{
$scriptblock = $ExecutionContext.InvokeCommand.NewScriptBlock("Remove-ADComputer $ThisComputer")
Invoke-Command -ComputerName SERVERNAME -ScriptBlock $scriptblock -Credential $AdCred
$ExistAfter = CheckExist
if($ExistAfter -ne $null){$RetValue = $false}
}
if($RetValue -ne $false)
{
Add-computer -domainname $domain -credential $Adcred -OUPath "OU=MyOU,DC=DOMAIN,DC=DE"
Restart-Computer -Force
}

If your domain controller runs Windows Server 2008 or higher you could leverage PowerShell sessions to avoid having to work with ADSI.
Just run the following command:
Enter-PSSession -ComputerName domaincontroller.test.com -Credential (Get-Credential)
Then run Import-Module ActiveDirectory to allow you to use Get-ADComputer and Remove-ADComputer.

Related

Create a script which disables a Windows Account on a target host. Only Admin can execute this action

I want to create a PowerShell script which will disable the windows account, the target host name will be provided as an argument. Only admin should be able to execute this task.
This is what I have tried. Could someone please tell me if this approach is right or is there any better way to do this.
param( [Parameter(Mandatory=$true)] [String] $TargetHost ,
[Parameter(Mandatory=$true)] [String] $TargetUserName ,
[String] $User ,
[String] $Password)
# Set up a trap to properly exit on terminating exceptions
trap [Exception] {
write-error $("TRAPPED: " + $_)
exit 1
}
function DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password){
$TargetHost = $TargetHost #Target Host on which windows account deactivation will be done.
$TargetUserName = $TargetUserName #User Name of Target.
$Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() #Domain name of the localhost.
$localHost = [System.Net.Dns]::GetHostName()
$localIP = [System.Net.Dns]::GetHostAddresses("$localHost")
#if TargetHost and LocalHost are same.
if($localHost -like $TargetHost -OR $localIP -like $TargetHost) {
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = net user $TargetUsername /domain /active:no #Performs the operation on the domain controller in the computer's primary domain.
} else {
$process = net user $TargetUsername /active:no
}
Write-host " $TargetUsername account deactivated "
}
#If TargetHost is remote Host.
else {
$User = $User #Creds to perform admin function.
$Password = $Password
$SecurePassword = new-Object System.Security.SecureString #Convert password into secure string.
$Password.ToCharArray() | % { $SecurePassword.AppendChar($_) }
$Cred = New-Object -typename System.Management.Automation.PSCredential -argumentlist "$User",$securePassword
$newSession = New-PSSession -ComputerName "$TargetHost" -credential $Cred #Used PSSession for persistent connection and credentials to Specify a user account that has permission to perform this action.
$export_username = Invoke-Command -Session $newSession -ScriptBlock {$username=args[1]} # Invoke-Command command uses the Session parameter(here newSession) to run the commands in same session.
if($Domain -eq [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()){
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /domain /active:no}
} else {
$process = Invoke-Command -Session $newSession -ScriptBlock {net user $username /active:no}
}
Write-host " $TargetUsername account deactivated "
Remove-PSSession $newSession # Closes Windows PowerShell sessions.
}
if(-not $?) { # Returns true if last command was successful.
Write-Error "Windows Deactivation Failed!!"
exit 1
}
}
DeactivateAccount($TargetHost , $TargetUserName ,$User , $Password)
Couple of things:
Your meant to show some code to show you tried but since you're new to Powershell I'll let that slide :)
Is it a local windows account you are trying to disable or an AD one? For the purpose of this I'll assume local.
Grab this module: https://gallery.technet.microsoft.com/PowerShell-Module-to-255637a3
The dude basically made a module for exactly what you want to do :)
Note: If you have Powershell 5.1+ you won't need the module they added new cmdlets to do this natively.
Credential-wise I wouldn't worry, Powershell can't bypass windows security, it will execute with the permissions of the user that ran the script unless your script specifically gives credentials for another user in the commands.
Let me know how you get on.

How to log into remote servers?

I currently have a VBScript that reads a list of servers, and attempts to verify the password of a specific userid. The userid is locally on that server. I am checking to see that the password is not set to the default (I want to make sure it was changed to something else).
The "list of servers" can be a mix of IP addresses, hostnames (like Rocky), or fully qualified DNS names (like rocky.bigcompany.com). The servers are a mixture of physical and virtual devices, and may or may not be on a domain.
The existing VBScript I wrote handles all this, and works fine. I'm trying to re-write this same program in Powershell, and It's not working.
Here's the function I have in VBScript that does what I want:
Function LoginToServer(Computer, username, password)
'this function will log into a server
On Error Resume next
Set locator = CreateObject("WbemScripting.SWbemLocator")
Set wmi = locator.ConnectServer(computer, "root\cimv2", username, password)
'check the error code and see if we logged in successfully
LoginRC = Err.Number
If LoginRC <> 0 Then
msg = "Could not log into server: " & CStr(computer) & " With ID: " & CStr(username)
lfo.lmsg "B", "WARN", msg
Else
msg = "Server: " & CStr(computer) & " Logged in successfully as: " & CStr(username)
lfo.lmsg "B", "INFO", msg
End If
wmi.Security_.ImpersonationLevel = 3
'return the code back to calleer
LoginToServer = LoginRC
End Function
… and here's what I've tried to do in PowerShell:
Param($ComputerName = "LocalHost")
$ErrorActionPreference = "Stop"
# Actual Code starts here
Write-Host "Attempting to ping server: $ComputerName"
$IPResult = Test-Connection -ComputerName $ComputerName -Quiet
if ($IPResult -eq "TRUE") {
Write-Host "Ping OK - now attempting to log in"
try {
$ID = "userid"
$PSW = "password"
$password = ConvertTo-SecureString $PSW -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential ($ID, $password)
$sesh = New-PSSession -ComputerName $ComputerName -Credential $cred
} catch {
Write-Host "Error caught"
$ErrorMessage = $_.Exception.Message
$FailedItem = $_.Exception.ItemName
} finally {
$Time = Get-Date
"$Time Computer: $ComputerName ERROR: $ErrorMessage ITEM: $FailedItem" |
Out-File c:\temp\TestCredScript.log -Append
}
} else {
Write-Host "Could not ping server"
}
How do I log into these remote computers with an ID and Password using PowerShell?
Your two code samples do different things. The VBScript code connects via WMI whereas the PowerShell code tries to establish a PowerShell session. For the latter you need PowerShell Remoting enabled, which you probably don't have.
While you probably may want to enable PSRemoting anyway, you can also use WMI from PowerShell. The Get-WmiObject cmdlet allows you to provide credentials and impersonation level, so you don't need to establish a connection first like you need to do with VBScript (if you want to use explicit credentials).
Example querying the Win32_Process class on a remote computer:
$computer = '...'
$username = 'userid'
$password = 'password'
$pw = ConvertTo-SecureString $password -AsPlainText -Force
$cred = New-Object Management.Automation.PSCredential ($username, $pw)
Get-WmiObject -Computer $computer -Namespace 'root\cimv2' -Class Win32_Process -Impersonation 3 -Credential $cred
See here for further information.

Issues automating printer driver update (printer settings) and printer preferences in Win7, using a PS,cmd,vbs,etc script?

WMI can do it, but I have an issue, PCs are on, but logged off. If I try to run:
wmic /node:%strIP% printer where DeviceID="lp1" set DriverName="Lexmark Universal v2"
It fails with a message about a "generic failure". I RDP in and then run the same command from my end, and it works. Powershell version I am using is older, so it does not have some of the printer cmdlets, and updating PS is currently out of the question. Is there a way to remotely log someone in, without actually having to RDP in? Via PS, cmd, PSEXEC, etc?
The other avenue I've taken is using regedit, but I'm hitting some hicups with that, namely that I cannot figure out what to copy. In regedit, I can change the drivername and the setting that enable duplex and tray2 (in printer settings), but I cannot figure how to change the settings in printer preferences for printing double sided and doing so along the long edge.
What I did to figure out what to change, I did a find on the printer name in regedit as a data value and exported the keys before changing the settings. Then I exported it again AFTER changing the settings. I then used fc /c /a /u before.reg after.reg to get the changes. I chopped up the .reg to include only the changed values. Running the .reg seems to change everything, but the print both sides, along the long edge settings. It is a lexmark printer, so I am wondering if maybe preferences for it are stored elsewhere.
This is my most up to date PS1 script. I've commented out some lines as I tried different ways of doing things:
$Cred = Get-Credential
$Str = Read-Host "Please select a site ID [###] "
$PC = Read-Host "Please select a PC number [##] "
Clear-Host
$PCNm = "$Str-CCPC-$PC"
function Test-PsRemoting
{
try
{
$errorActionPreference = "Stop"
$result = Invoke-Command -ComputerName $PCNm { 1 }
}
catch
{
Write-Verbose $_
return $false
}
if($result -ne 1)
{
Write-Verbose "Remoting to $PCNm returned an unexpected result."
return $false
}
$true
}
If(!(Test-PsRemoting)){
PSEXEC \\$PCNm powershell Enable-PSRemoting -force 2>&1 >nul
Clear-Host
Write-Host "Enabled PsRemoting"
}else{Write-Host "PsRemoting already enabled"}
Invoke-Command -ComputerName $PCNm -Credential $Cred -ScriptBlock {
#$lp1 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
$lp1 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp1"}
$lp1.Scope.Options.EnablePrivileges = $true
$lp1.DriverName = "Lexmark Universal v2"
$lp1R = $lp1.Put()
#$lp2 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp2'"
$lp2 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp2"}
$lp2.Scope.Options.EnablePrivileges = $true
$lp2.DriverName = "Lexmark Universal v2"
$lp2R = $lp2.Put()
}
#$lp1 = Get-WMIObject -Impersonation Delegate -Authentication Call -Credential $Cred -ComputerName $PCNm -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
#$lp1.DriverName = "Lexmark Universal v2"
#$lp1.Put()
No matter which way I try it, invoke-command, or get-wmiobject, I get:
Exception calling "Put" with "0" argument(s): "Generic failure "
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
+ PSComputerName : 150-CCPC-02
This doesn't particularly answer your actual question but as a solution for how I do this very same thing I thought I would give you what I threw together to update printer properties. I have not cleaned this up at all as I was porting it from my create printer function.
Function Set-SSPrinter {
Param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[string]$PortName,
[string]$DriverName,
[string]$Comment,
[string]$Location,
[bool]$Shared,
[string]$ShareName = $Name,
[string]$PermissionSDDL,
[string]$PrintProcessor,
[string]$DataType,
[bool]$RawOnly
)
try {
$modprinter = Get-WmiObject Win32_Printer -ComputerName $ComputerName | ?{$_.name -eq $Name}
$modprinter.Scope.Options.EnablePrivileges = $true
if($DriverName) {
$modprinter.DriverName = $DriverName
}
if($PortName) {
$modprinter.PortName = $PortName
}
if($Shared) {
$modprinter.Shared = $Shared
}
if($ShareName) {
$modprinter.ShareName = $ShareName
}
if($Location) {
$modprinter.Location = $Location
}
if($Comment) {
$modprinter.Comment = $Comment
}
if($Name) {
$modprinter.DeviceID = $Name
}
if($PrintProcessor) {
$modprinter.PrintProcessor = $PrintProcessor
}
if($DataType) {
$modprinter.PrintJobDataType = $DataType
}
if($RawOnly) {
$modprinter.RawOnly = $RawOnly
}
$result = $modprinter.Put()
if($PermissionSDDL) {
$modprinter.SetSecurityDescriptor($objHelper.SDDLToWin32SD($PermissionSDDL).Descriptor) | Out-Null
}
$("Update Complete: " + $Name)
} catch {
$("Update Failed: " + $Name)
Write-Warning $_.Exception.Message
$error.Clear()
}
}
Unfortunately I use the printer name to figure out which device to modify on the remote machine. Your executing credentials from the powershell session you have open must have admin rights on the remote machine. if necessary do a runas different user on powershell.exe
Example usage:
Set-SSPrinter -ComputerName "10.210.20.100" -Name "TestPrinter" -DriverName "Lexmark Universal v2"
wmic /node:servername /user:username /password:password path win32_something call methodname
Is how to do it.
Things with users are best done with logon scripts because that is how windows is designed.

Upgraded to PS3, System.Net.WebClient not working on some machines

I am running in a lab environment and need to automate about 50 machines. I am trying to recover an .xml wireless network profile from a server then install it. This command is being sent from 1 server to the 50 clients.
I recently reimaged my some of my clients and upgraded from PS2 to PS3 and now my Download script is not working anymore.
It does work fine on my PS2 workstations. I am assuming that it might be a permission thing, but I'm not sure. ThrustedHosts is set to * and Script execution policy is set to Unrestricted.
Here's a snippet and the error:
function InstallProfile(){
clear
$fonction =
#'
param($profileName)
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
Write-Host (hostname)
$webclient.DownloadFile($uri, $File)
write-host (hostname) (netsh wlan add profile filename="c:\profiles\profile.xml")
'#
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
ExecCmd -fonction $fonction -argument $profileName
func_done
}
#
function ExecCmd
{
param(
$fonction,
$argument
)
$PingTest = RetrieveStatus
$results = #{}
$results = $PingTest.up
$results | sort -uniq | out-Null
$fonctionSB = ConvertTo-ScriptBlock($fonction)
foreach($result in $results)
{
$os = "Windows"
try{
$session = New-PSSession -ComputerName $result.address -Credential $credentials -EA stop
}
catch{
$os = "Not Windows"
}
if($os -eq "Windows"){
Invoke-Command $result.address -ScriptBlock $fonctionSB -Arg $argument -Credential $credentials
Get-PSSession | Remove-PSSession
}
else{
Write-Host $result.address "does not support Powershell commands"
}
}
}
And the error:
Exception calling "DownloadFile" with "2" argument(s): "An exception occurred during a WebClient request."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException
+ PSComputerName : 192.168.2.110
I found a workaround,
I added a try/catch for the webclient.Download, which works for PS2. In the catch portion, I replaced the command with Invoke-WebRequest $uri -OutFile $File
Works fine for PS2 and PS3 this way!

Powershell - Some commands won't run with Invoke-Command

I am trying to send some commands from a server to about 50 clients running Powershell. Most commands work using Invoke-Command. I used the exact same format as my other commands, yet this one won't work. Basically I want to have each client fetch an .xml file from my server to import it later on. I am missing $credentials and other variables from my code sample here but they are setup correctly somewhere else in my script.
Permission wise, TrustedHosts in winrm is set to * and script execution is set to Unrestricted.
clear
$temp = RetrieveStatus
$results = $temp.up #Contains pinged hosts that successfully replied.
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
$command = {write-host (hostname) $webclient.DownloadFile($uri, $File)}
foreach($result in $results)
{
# download profile from C:\share\profiles
Invoke-Command $result.address -ScriptBlock $command -Credential $credentials
# add profile to wireless networks
# Invoke-Command $result.address -ScriptBlock {write-host (hostname) (netsh wlan add profile filename="c:\profiles\$args[0].xml")} -argumentlist $profileName -Credential $credentials
}
I get the following error:
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (DownloadFile:String) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Any idea? The same command works flawlessly on the clients when run locally.
You use $webclient within a scriptblock where $webclient will not be defined on the other end. Why don't you create the web client in the scriptblock e.g.:
$command = {
param($profileName)
$File = "c:\profiles\profile.xml"
$webclient = New-Object System.Net.WebClient
$webclient.Proxy = $NULL
$ftp = "ftp://anonymous:anonymous#192.168.2.200/profiles/$profileName"
$uri = New-Object System.Uri($ftp)
Write-Host (hostname)
$webclient.DownloadFile($uri, $File)}
}
$profileName = Read-Host "Enter the profile name(XML file must be present in c:\share\profiles\)"
Invoke-Command $result.address -ScriptBlock $command -Credential $credentials -Arg $profileName
This will require you to provide some of the variables from the client to the remote machine via the -ArgumentList parameter on Invoke-Command. Those supplied arguments then map to the param() statement in the scriptblock.