Need to write a script to take an input file (text) list of names, check if it exists in AD, and create new computers.
The requirements are as follows -
Computer names are based on the users name (input from file)
Names must be 15 characters (for name resolution)
if the truncated name doesnt exist, create a computer object in specific OU with the truncated name.
If the truncated name does exist, append -# and test to see if it exists until it finds one that doesnt, then create new computer object with that name.
At the end I will need to output the results to an array but I haven't started adding that yet since this doesn't work.
So I finally got the "else" part but the if part at the beginning does not work.
$users = get-content C:\scriptdata\VMS.txt
$OU = ************
foreach ($user in $users)
{
$cleanname = if ($user.Length -gt 15) { $user.Substring(0, 15) } else { $user }
$exist = (get-adcomputer $cleanname) -eq $null
if ((get-adcomputer $cleanname) -like "get-adcomputer : Cannot find an object with identity")
{
New-ADComputer -Name $cleanname -Path "$OU" -SAMAccountName $cleanname -confirm
}
else
{
$count=0
DO{
$count++
$cleanname13 = if ($user.Length -gt 13) { $user.Substring(0, 13) } else { $cleanname }
$cleannamedash = $cleanname13 + '-' + "$count"
}
UNTIL ((get-adcomputer $cleannamedash | out-null) -eq $null)
New-ADComputer -Name $cleannamedash -Path "$OU" -SAMAccountName $cleannamedash -confirm
}
}
currently works for -# but not for those that dont exist at all.
Have a look at Naming conventions in Active Directory for computers, domains, sites, and OUs.
You'll find that there is more to a valid computer name than just the length.
Mind that the New-ADComputer cmdlet creates a new computer object, but does not join a computer to a domain.
Something like this should work (untested)
$computers = Get-Content C:\scriptdata\VMS.txt | Where-Object { $_ -match '\S'}
$OU = ************
foreach ($name in $computers) {
$newName = ($name -creplace '[\\/:*?"<>|.]','').Substring(0, 15)
try {
$computer = Get-ADComputer -Filter "Name -eq '$newName'" -PassThru -ErrorAction Stop
}
catch {
$computer = $null
}
if ($computer) {
# a computer with that name already exists, create a new name by adding a dash and two digit number
$count = 0
$name12 = $newName.Substring(0, 12) # we're going to add three characters
# get an array of computernames that are like the one you want to create
$existingComputers = Get-ADComputer -Filter "Name -like '$name12-*'" | Select-Object -ExpandProperty Name
do {
$newName = '{0}-{1:00}' -f $name12, ++$count
}
until ($existingComputers -notcontains $newName -or $count -gt 99)
if ($count -gt 99) {
$newName = '{0}-XX' -f $name12
throw "Cannot create computer $newName because all index numbers 00..99 are taken.."
}
}
# use splatting, because New-ADComputer has MANY parameters
$props = #{
'Name' = $newName
'Path' = $OU
'SamAccountName' = $newName
'Enabled' = $true
'Confirm' = $true
}
Write-Host "Creating computer '$newName'"
New-ADComputer #props
}
I assume you mean that this is the line that's not working:
if ((get-adcomputer $cleanname) -like "get-adcomputer : Cannot find an object with identity")
And even this doesn't work:
$exist = (get-adcomputer $cleanname) -eq $null
The reason is the same in both cases: If the computer doesn't exist, then Get-ADComputer throws an exception and the comparison is never done.
There is a good article about this here, but in short, the solution is to catch the exception. For you, it would look something like this:
try {
$computer = Get-ADComputer $cleanname
# If we get here, we know it exists
# You can put your loop here and just keep looping until Get-ADComputer throws an exception
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
# Whatever you last tried doesn't exist. Create it here.
}
Related
I have written a script to pull permissions from file directories so that we can audit access to folders. I just want to see what groups we have not what users so I wrote this script to pull out all group names and remove the domain name from the value so that it can then run it through a second script that corrects the AD group name for us if its incorrect at all since we ran into an issue where for some reason some were coming back with slightly different names. The problem is all the AD users named in permissions come back as errors. I want those errors to not even show up on screen is there a way to do that? As you can see I have been trying a few different ways to pipe them to a log or the -ea ignore option but it still shows the errors on screen.
$filelocationscsv = "C:\AD\Excel\File Share migration.csv"
$filelocationcsvcontents = Get-Content -LiteralPath $filelocationscsv
$AllFolders = #()
foreach ($location in $filelocationcsvcontents) {
$AllFolders += $location.Substring(0,$location.Length-1)
}
$outputfilelocation = "C:\AD\Excel\permissions.csv"
$Results = #()
$errResults = #()
Foreach ($i in $Allfolders) {
if (Test-Path $i){
Write-Host "Obtaining file permissions for $i."
$acl = (Get-Acl $i -Filter *).Access | select -ExpandProperty IdentityReference
foreach($Access in $acl) {
if ($Access.Value -notlike "BUILTIN\Administrators" -and $Access.Value -notlike "domain\Domain Admins" -and $Access.Value -notlike "CREATOR OWNER" -and $access.Value -notlike "NT AUTHORITY\SYSTEM" -and $access.Value -notlike "Everyone" -and $access.Value -notlike "BUILTIN\Users" -and $access.Value -notlike "s-1*") {
[string]$perm = $Access.Value.Split('\')[1]
if($checkgroup = Get-ADGroup $perm){
#try
#{
## if( $LASTEXITCODE -gt 0 ){
## # Handle the error here
## # This example writes to the error stream and throws a terminating error
## $errResults += $LASTEXITCODE
## Write-Error "Unable to ping server, ping returned" -EA Ignore
## }
$Properties = [ordered]#{'AD Group'=$perm}
$Results += New-Object -TypeName PSObject -Property $Properties
#}
#Catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
#{
# Write-Verbose "$perm skipped." -Verbose
# #$ErrorMessage =
# #$FailedItem = $_.Exception.ItemName
# #$errResults += $ErrorMessage + $FailedItem
#}
}
}
}
}
else {
Write-Host "$i is not accessible"
}
}
$Results | select -Property 'AD Group' -Unique | Export-Csv $outputfilelocation -NoTypeInformation
Its worth noting these errors do not stop my script from running its more of an aesthetic function as well as a learning opportunity for myself. I can use my script like it is but I would love to make it look cleaner and learn how to handle errors better.
As you
indicate you are interested in learning more about error handling, one thing I learned this week are these common Parameters for error handling and recording:
-ErrorAction
-WarningAction
-ErrorVariable
-WarningVariable
You can silence the error messages by using the parameter -ErrorAction SilentlyContinue but capture the error by using the parameter -ErrorVariable
EXAMPLE: get-adgroup -ErrorAction SilentlyContinue -ErrorVariable MyErrors
You can read and manipulate the errors by calling $MyErrors
The warnings work the same way
It might give an alternative to Try/Catch.
Thank you #pwnosh you're a genius!
I changed line 20 to
if($errResults += try {$checkgroup = Get-ADGroup $perm -ErrorAction Stop } catch {[Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]}){
This line forces the users into my CSV as well but then the second script cleans them out anyways with
$results = #()
foreach($Group in ($Groups = Import-csv C:\AD\Excel\permissions.csv)){
$groupname = $Group.'AD Group'
Write-Host "Confirming $groupname group name in AD."
$results += get-adgroup -Filter "name -like '$groupname'" -Properties * -SearchBase "dc=domain,dc=local,dc=net" | select name
}
$results | Export-Csv C:\AD\Excel\ADGroups.csv -NoTypeInformation
I have an existing script which does the job of checking if a given user exists in AD or not. But I'm unable to export the result in csv file. Please help.
Clear-Host
$UserList = gc .\Output_userInfo.csv
$outputFilePath = "D:\Input\User&Group_Output.csv"
foreach ($u in $UserList) {
try {
$ADUser = Get-ADUser -Identity $u -ErrorAction Stop
}
catch {
if ($_ -like "Cannot find an object with identity: '$u'") {
"User '$u' does not exist." | Export-Csv .\notexists.csv -NoTypeInformation -Force
}
else {
"An error occurred: $_"
} continue
}
"User '$($ADUser.SamAccountName)' exists." |
Export-Csv .\notexists.csv -NoTypeInformation -Force
}
$UserList = gc C:\temp\Output_userInfo.csv #use full path instead. .\ is relative path and could cause issues if you are not careful
$outputFilePath = "D:\Input\User&Group_Output.csv"
$finalResult = foreach ($u in $UserList)
{
#CSV takes data in a table format. So best to replicate that with a PS Cusotm object that can easily be represented ina table format.
$obj = [PSCustomObject]#{
UserName = $u
Status = ""
}
try
{
$ADUser = Get-ADUser -Identity $u -ErrorAction Stop
$obj.Status = "Exists"
}
catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException]
{
$obj.Status = "Does not Exist"
}
catch
{
$obj.Status = $_.Exception.Message
}
$obj
}
$finalResult | Export-Csv -Path $outputFilePath -NoTypeInformation -Force
If you are wondering how I knew the error type used in the 1st catch, you can find it by simulating an error [in this case, get-aduser blah would do it since such a user does not exist]. Then you can expand the last error message with select * as shown and look at the exception type. Alternately, you could also try to read the documentation but I don't have that kind of patience.
I know this is pulling quite a bit of data, but at present it's capping my memory consumption when I run it on my local machine. The good news is, it's returning the output that I need. Can someone help me with performance optimization? So far, I haven't done much for fear of messing up a script that returns my desired output. Thanks in advance for any suggestions.
#// Start of script
#// Get year and month for csv export file
$DateTime = Get-Date -f "yyyy-MM"
#// Set CSV file name
$CSVFile = "C:\Temp\AD_Groups"+$DateTime+".csv"
#// Create emy array for CSV data
$CSVOutput = #()
Measure-Command {
#// Get all AD groups in the domain
$ADGroups = Get-ADGroup -Filter "GroupScope -ne 'DomainLocal' -AND GroupCategory -eq 'Security' -AND Member -like '*'" -SearchBase "OU=SHS, DC=shs, DC=net" -Properties Member #-ResultSetSize 1000 Name -like '*''s*' -AND
#// Set progress bar variables
$i=0
$tot = $ADGroups.count
foreach ($ADGroup in $ADGroups) {
#// Set up progress bar
$i++
$status = "{0:N0}" -f ($i / $tot * 100)
Write-Progress -Activity "Exporting AD Groups" -status "Processing Group $i of $tot : $status% Completed" -PercentComplete ($i / $tot * 100)
#// Ensure Members variable is empty
$Members = ""
#// Get group members which are also groups and add to string
$MembersArr = Get-ADGroup $ADGroup.DistinguishedName -Properties Member | Select-Object -ExpandProperty Member
if ($MembersArr) {
foreach ($Member in $MembersArr) {
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member}
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$UserObj = Get-ADObject -filter {DistinguishedName -eq $Member}
$match = $UserObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($UserObj.Enabled -eq $False) {
continue
}
$Members = $empid
}
# Check for null members to avoid error for empty groups
if ([string]::IsNullOrEmpty($Members)) {
continue
}
$HashTab = [ordered]#{
"GroupName" = $ADGroup.Name -replace "'s", "''s"
"GroupCategory" = $ADGroup.GroupCategory
"GroupScope" = $ADGroup.GroupScope
"MemberID" = if([string]::IsNullOrEmpty($empid)){""}
else{$empid}
}
#// Add hash table to CSV data array
$CSVOutput += New-Object PSObject -Property $HashTab
}
}
#// Export to CSV files
$CSVOutput | Sort-Object Name, Member | Export-Csv $CSVFile -NoTypeInformation
}
}
I've experienced this too with code that loops through thousands of accounts. The problem is that the garbage collector doesn't have time during the loop to clean up, since your code is constantly doing something. In .NET, I'd call .Dispose() manually to make sure stuff is cleaned up, but here you can't.
You can try calling [System.GC]::Collect() after you assign each variable in the loop. For example, after $MembersArr = and after $ADObj = to (hopefully) make it deallocate the memory used for the previous value.
Also, I think that $UserObj = Get-ADObject... line should be calling Get-ADUser, not Get-ADObject. As it is, $UserObj.Enabled will never have a value and your continue will never be hit.
But you can save yourself the use of Get-ADUser entirely by asking for the userAccountControl value in Get-ADObject and using that to determine if the user is disabled. For example:
$ADObj = Get-ADObject -filter {DistinguishedName -eq $Member} -Properties userAccountControl
# Clean up the old $ADObj value
[System.GC]::Collect()
#// Initialize regex variable
$matches = ""
if ($ADObj.ObjectClass -eq "user") {
$match = $ADObj -match '\([a-zA-Z0-9]+\)'
$empid=$matches[0] -replace ".*\(","" -replace "\)",""
if ($ADObj.userAccountControl -band 2) {
continue
}
$Members = $empid
}
The $ADObj.userAccountControl -band 2 condition checks is a bitwise AND comparison to check if the second bit of the userAccountControl value is set, which means that the account is disabled.
Its pretty "simple" what i want to achieve. I have people creating Computer Objects on my AD and leaving there without moving them to the appropiate OU.
I would like a powershell script to read the list of computers from the Computers OU, and depending the first 5 or 6 letters from the Computer name, move it to the appropiate OU, reading the list of destination OUs from a CSV or txt or whatever file type.
I need to move more than 100 computers and I would like to scan them, and move them to their corresponding OU.
I've thought to use a variable for the computer accounts, then a foreach and a switch or something similar, and 1-by-1 start moving the accounts. But I'm stuck.
Thank you!!!!
Turning my comment into an answer. You could create a lookup Hashtable for this:
# create a lookup Hashtable for all OU's in your organisation
# You can limit this using parameters like '-SearchScope' and '-SearchBase' depending on the structure in your AD environment
$allOUs = #{}
Get-ADOrganizationalUnit -Filter 'Name -like "*"' | ForEach-Object {
$allOUs[$_.Name] = $_.DistinguishedName
}
# next, get all computers in the default Computers OU
$result = Get-ADComputer -Filter * -SearchBase "CN=Computers,DC=Contoso,DC=com" | ForEach-Object {
$computerName = $_.Name
$found = $false
if ($computerName.Length -ge 6) {
$targetOU = $computerName.Substring(0,6)
$found = $allOUs.ContainsKey($targetOU)
}
if (!$found -and $computerName.Length -ge 5) {
$targetOU = $computerName.Substring(0,5)
$found = $allOUs.ContainsKey($targetOU)
}
if ($found) {
try {
$_ | Move-ADObject -TargetPath $allOUs[$targetOU] -ErrorAction Stop -WhatIf
# add success to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Moved'
}
}
catch {
# add exception to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Not moved. {0}' -f $_.Exception.Message
}
}
}
else {
# add failure to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = ''
'Result' = 'Not moved. Computername does not begin with a valid OU name'
}
}
}
# output on screen
$result
# output to file
$result | Export-Csv -Path 'ComputersMoved.CSV' -NoTypeInformation
Remove the -WhatIf switch if you are satisfied with the results shown in the console.
This should be dynamic enough. You can replace the Map object with a CSV.
$Map = [PSCustomObject]#{
AABBCC = "OU=ABC,DC=Contoso,DC=com";
CCBBAA = "OU=CBA,DC=Contoso,DC=com"
}
$Prefixlist = ($Map.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" }).Name
$Report = #()
$MissingPrefix = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
if ($Prefixlist -contains $Prefixlist) {
try {
$obj | Move-AdObject -Targetpath $Map.$Prefix -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
else {
$MissingPrefix += $Prefixlist
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize
"Not found prefix list"
$MissingPrefix
Option 2 to make the path based on the prefix
$Report = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
try {
$obj | Move-AdObject -Targetpath "OU=Computers,OU=$Prefix,DC=Contoso,DC=com" -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize
I'm trying to create a loop that tests the connection to specific computers in the Active Directory. The output is #{name=KY-WH-DT01} when I'm looking for KY-WH-DT01. I'm not sure what I'm doing wrong.
As a workaround, I've pulled the list manually and properly inserted it into the variable as hard code.
function testConnection {
$computers = Get-ADComputer -filter 'Name -like "KY-WH*" -or name -like "KY-SR"' | select name
$pass = 0
$fail = 0
foreach ($computer in $computers) {
$testConnection = Test-Path "\\$computer\C$" -ErrorAction Stop
if ($testConnection -eq $true) {
Write-Host $computer -ForegroundColor Green
$pass = $pass + 1
}
else {
Write-Host $computer -ForegroundColor Red -BackgroundColor Black
$fail = $fail + 1
}
}
Write-Host $null
Write-Host "Passed: $pass | Failed: $fail"
}
testConnection
...
This code should output a list of computer names with colors determining whether the connection test passed or failed by turning them either red or green.
You need to drill down a little bit in your variable.
foreach ($computer in $computers.Name) {
This will do it if you only want the name or the computer and no other variable.
You can also change your initial search to include the -ExpandProperty switch and you will not need to dig down into the property.
$computers = Get-ADComputer -filter 'Name -like "KY-WH*" -or name -like "KY-SR"' | select -ExpandProperty name