Struggling with If Statements - powershell

I have problems of understanding values of variables in PowerShell and I check them with if statements.
$LDAPDirectoryService = '10.10.XXX.XXX:389'
$DomainDN = 'o=Enterprise'
#$LDAPFilter = '(&(objectCategory=Person)(memberOf=cn=alc-01-Planung-rw,ou=KT,o=enterprise))'
$LDAPFilter = '(&(cn=alc-01-Planung-rw))'
$null = [System.Reflection.Assembly]::LoadWithPartialName('System.Net')
$LDAPServer = New-Object System.DirectoryServices.Protocols.LdapConnection $LDAPDirectoryService
$LDAPServer.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous
$Scope = [System.DirectoryServices.Protocols.SearchScope]::Subtree
$AttributeList = #('*')
$SearchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $DomainDN,$LDAPFilter,$Scope,$AttributeList
$groups = $LDAPServer.SendRequest($SearchRequest)
$groups
if ($groups -eq $null) {"No Group found"}
if ($groups -eq " ") {"No Group found"}
foreach ($group in $groups.Entries) {
$users = $group.attributes['member'].GetValues('string')
foreach ($user in $users) {
Write-Host $user
}
}
I want to check if the group exists and then if users are existing in this group. I tried many statements but none of them seem to work.
It's not null or blank, even when nothing is written down in the console.
This is what I got when I use group which doesn't exist:
Can anybody show me a solution?

What version of PowerShell are you running? Why are you not using the built-in AD group cmdlets for this or are you not using ADDS but some other LDAP service?
Or you may be on OSX/Linux and are using PSCore, which the ADDS/RSAT cmdlets are not there, well, not yet?
For your goals of …
I want to check if the group exists and then if users are existing in
this group.
… On Windows, with PowerShell 3x or higher, it's really only this...
# Get all AD groups and all members of each group
Clear-Host
(Get-ADGroup -Filter '*').Name |
%{
"`n*** The members of $PSItem are as follows: ***`n"
If((Get-ADGroupMember -Identity $PSItem).Count -ge 1)
{
(Get-ADGroupMember -Identity $PSItem).SamAccountName
}
Else
{
Write-Warning -Message "$PSItem does not exist or has no members."
}
}
# Filtered
Clear-Host
((Get-ADGroup -Filter '*').Name -match 'Domain Admins|Domain Users' ) |
%{
"`n*** The members of $PSItem are as follows: ***`n"
If((Get-ADGroupMember -Identity $PSItem).Count -ge 1)
{
(Get-ADGroupMember -Identity $PSItem).SamAccountName
}
Else
{
Write-Warning -Message "$PSItem does not exist or has no members."
}
}
Using your LDAP approach though... How about this...
'Administrators','Distributed COM Users' |
ForEach {
# Define LDAP search root, the Global catalog of the domain
$sLDAPSearchRoot = "LDAP://$((Get-ADDomainController).IPv4Address):389"
# The Groupname to looking for
($sGroupName = "$_")
# The LDAP query - query string
$sSearchStr = "(&(objectCategory=group)(name="+$sGroupName+"))"
# Get the search object
$oSearch = New-Object directoryservices.DirectorySearcher($oADRoot,$sSearchStr)
# Looking for the group
$oFindResult = $oSearch.FindAll()
# On success, get a DirectoryEntry object for the group
$oGroup = New-Object System.DirectoryServices.DirectoryEntry($oFindResult.Path)
# And list all members
If (($oGroup.Member).Count -ge 1)
{
$oGroup.Member |
%{($oMembers = New-Object System.DirectoryServices.DirectoryEntry($sLDAPSearchRoot+"/"+$_))}
}
Else
{ Write-Warning -Message "$($oGroup.Member) does not exist or has no members"}
}

Related

How to speed up the process in PowerShell

I've a simple script that generate orphan OneDrive report. It's working but it's very slow so I'm just wondering how could I modify my script to speed up the process. Any help or suggestion would be really appreciated.
Basically this is what I'm doing:
Get the owner email from "Owner" column and check it using AzureAD to see if I get any error
If I get an error then check it in on-prem ADGroup
If that owner is existed in the on-prem ADGroup then it's an orphan
Export only that user to a new csv file
$ImportData = "E:\scripts\AllOneDriveUser.csv"
$Report = "E:\scripts\OrphanOneDrive.csv"
$CSVImport = Import-CSV $ImportData
ForEach ($CSVLine in $CSVImport) {
$CSVOwner = $CSVLine.Owner
try{
Get-AzureADUser -ObjectId $CSVOwner
}catch{
$StatusMessage = $_.Exception.Message
if($Null -eq $StatusMessage){
Write-Host "User Found, Ignore from orphan list."
}else{
#Owner not found in AzureAD
$group = 'TargetGroup'
$filter = '(memberof={0})' -f (Get-ADGroup $group).DistinguishedName
$filterName = Get-ADUser -LDAPFilter $filter
$ModifiedOwner = $CSVOwner -split"#"[0]
if( $ModifiedOwner[0] -in $filterName.Name ){
Write-host "Adding it into orphaned list"
$CSVLine | Export-Csv $Report -Append -notypeinformation -force
}else{
Write-Host "Not orphaned"
}
}
}
}
I have over 8000 record in my import csv file and over 5000 member in my on-prem AD group so it taking very long.
You can greatly improve your script by using a HashSet<T> in this case, but also the main issue of your code is that you're querying the same group over and over, it should be outside the loop!
There is also the use of Export-Csv -Append, appending to a file per loop iteration is very slow, better to streamline the process with the pipelines so Export-Csv receives the objects and exports only once instead of opening and closing the FileStream everytime.
Hope the inline comments explain the logic you can follow to improve it.
$ImportData = "E:\scripts\AllOneDriveUser.csv"
$Report = "E:\scripts\OrphanOneDrive.csv"
# we only need to query this once! outside the try \ catch
# a HashSet<T> enables for faster lookups,
# much faster than `-in` or `-contains`
$filter = '(memberof={0})' -f (Get-ADGroup 'GroupName').DistinguishedName
$members = [Collections.Generic.HashSet[string]]::new(
[string[]] (Get-ADUser -LDAPFilter $filter).UserPrincipalName,
[System.StringComparer]::OrdinalIgnoreCase
)
Import-CSV $ImportData | ForEach-Object {
# hitting multiple times a `catch` block is expensive,
# better use `-Filter` here and an `if` condition
$CSVOwner = $_.Owner
if(Get-AzureADUser -Filter "userprincipalname eq '$CSVOwner'") {
# we know this user exists in Azure, so go next user
return
}
# here is for user not found in Azure
# no need to split the UserPrincipalName, HashSet already has
# a unique list of UserPrincipalNames
if($hash.Contains($CSVOwner)) {
# here is if the UPN exists as member of AD Group
# so output this line
$_
}
} | Export-Csv $Report -NoTypeInformation

Pass a variable as a switch parameter in Powershell

I'm trying to create a powershell script that will grab all Active Directory accounts that are enabled, and inactive for 90 days. The script will prompt the user to choose between querying computer or user accounts.
Depending on the choice, it will pass it over to the main command as a variable.
The commands work correctly if I don't pass a variable.
I'm not sure if what I'm trying to do is possible.
Sorry for any bad code formatting. Just starting out.
Clear-Host
write-host "`nProgram searches for Enabled AD users account that have not logged in for more than 90 days. `nIt searches the entire domain and saves the results to a CSV file on users desktop." "`n"
$choice = Read-host -Prompt " What do you want to search for Computer or Users Accounts`nType 1 for users`nType 2 for Computers`n`nChoice"
$account
if ($choice -eq 1) {
$account = UsersOnly
}
Elseif ($choice -eq 2) {
$account = ComputersOnly
}
Else {
write-host "This is not an option `n exiting program"
exit
}
$FileName = Read-Host -Prompt "What do you want to name the CSV file"
$folderPath = "$env:USERPROFILE\Desktop\$FileName.csv"
Search-ADAccount -AccountInactive -TimeSpan 90 -$account | Where-Object { $_.Enabled -eq $true } | select Name, UserPrincipalName, DistinguishedName | Export-Csv -Path $folderPath
Splatting is the way to achieve this. It's so named because you reference a variable with # instead of $ and # kind of looks a "splat".
it works by creating a hashtable, which is a type of dictionary (key/value pairs). In PowerShell we create hashtable literals with #{}.
To use splatting you just make a hashtable where each key/value pair is a parameter name and value, respectively.
So for example if you wanted to call Get-ChildItem -LiteralPath $env:windir -Filter *.exe you could also do it this way:
$params = #{
LiteralPath = $env:windir
Filter = '*.exe'
}
Get-ChildItem #params
You can also mix and match direct parameters with splatting:
$params = #{
LiteralPath = $env:windir
Filter = '*.exe'
}
Get-ChildItem #params -Verbose
This is most useful when you need to conditionally omit a parameter, so you can turn this:
if ($executablesOnly) {
Get-ChildItem -LiteralPath $env:windir -Filter *.exe
} else {
Get-ChildItem -LiteralPath $env:windir
}
Into this:
$params = #{
LiteralPath = $env:windir
}
if ($executablesOnly) {
$params.Filter = '*.exe'
}
Get-ChildItem #params
or this:
$params = #{}
if ($executablesOnly) {
$params.Filter = '*.exe'
}
Get-ChildItem -LiteralPath $env:windir #params
With only 2 possible choices, the if/else doesn't look that bad, but as your choices multiply and become more complicated, it gets to be a nightmare.
Your situation: there's one thing I want to note first. The parameters you're trying to alternate against are switch parameters. That means when you supply them you usually only supply the name of the parameter. In truth, these take boolean values that default to true when the name is supplied. You can in fact override them, so you could do Search-ADAccount -UsersOnly:$false but that's atypical.
Anyway the point of mentioning that is that it may have been confusing how you would set its value in a hashtable for splatting purposes, but the simple answer is just give them a boolean value (and usually it's $true).
So just changing your code simply:
$account = if ($choice -eq 1) {
#{ UsersOnly = $true }
} elseif ($choice -eq 2) {
#{ ComputersOnly = $true }
}
# skipping some stuff
Search-ADAccount -AccountInactive -TimeSpan 90 #account
I also put the $account assignment on the left side of the if instead of inside, but that's your choice.

Get local group members: version agnostic

I found this thread that offers two basic approaches to getting local group members.
This works for me in all versions of powershell, but depends on using the old NET command line utility.
function Get-LocalGroupMembers() {
param ([string]$groupName = $(throw "Need a name") )
$lines = net localgroup $groupName
$found = $false
for ($i = 0; $i -lt $lines.Length; $i++ ) {
if ( $found ) {
if ( -not $lines[$i].StartsWith("The command completed")) {
$lines[$i]
}
} elseif ( $lines[$i] -match "^----" ) {
$found = $true;
}
}
}
This works for me in PowerShell 2.0, but barfs in PS5.0 with Error while invoking GetType. Could not find member.
It only barfs on some groups, including Administrators, which has me thinking it's some sort of security feature, like requiring elevated privileges to REALLY have admin rights in a script.
Function Get-LocalGroupMembers
{
Param(
[string]
$server = "."
)
Try
{
$computer = [ADSI]"WinNT://$( $Server ),computer"
$computer.psbase.children |
where {
$_.psbase.schemaClassName -eq 'group'
} |
ForEach {
$GroupName = $_.Name.ToString()
$group =[ADSI]$_.psbase.Path
$group.psbase.Invoke("Members") |
foreach {
$memberName = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -replace "WinNT:", ""
$props = #{
"LocalGroup" = $GroupName
"MemberName" = $memberName
}
$obj = New-Object -TypeName psobject -Property $props
Write-Output $obj
} # foreach members
} # foreach group
}
Catch
{
Throw
}
}
I think I read somewhere that PS5.1 has a native CMDlet finally. But I can't depend on a particular version of PS, I need to support everything from PS2.0 in Win7 up. That said, is there a single version agnostic solution that doesn't depend on a command line utility kludge? Or do I need to have code that uses the old hack or the new CMDlet depending on PS version I am running on?
So, I am having good luck with this solution.
$hostname = (Get-WmiObject -computerName:'.' -class:Win32_ComputerSystem).name
$wmiQuery = Get-WmiObject -computerName:'.' -query:"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$Hostname',Name='$group'`""
if ($wmiQuery -ne $null) {
:searchLoop foreach ($item in $wmiQuery) {
if (((($item.PartComponent -split "\,")[1] -split "=")[1]).trim('"') -eq $user) {
$localGroup = $true
break :searchLoop
}
}
}
I'm not sure yet if I like that overly complex IF vs some variables, but the functionality is there and working across all versions of PS without resorting to command line kludges, which was the goal.
Note that this just returns true if the user is a member of the group, which is all I need. The other code I posted would provide a list of members, which is the basis of doing a check, and I just hadn't modified it to show the real end goal, since the problem manifested without that.
Instead this :
$memberName = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -replace "WinNT:", ""
You can try this :
$memberName = ([ADSI]$_).InvokeGet("Name")

WMI pulls current user even if never logged in

I am new to powershell, and I am attempting to create a CSV from an excel file for comparison sake to see who is currently logged into a computer. However, I'm running into an odd issue where sometimes the script will pull the same user multiple times even if they've never logged into a computer. Here is my full code. I know there is a lot of optimization that can be done (and parts that need to be removed, those should be noted). I assume I've misused Get-WMIObject or something similar, can anyone see why it would pull that information like it is?
$csvRunFile = "test.csv"
$output = "Results_$(Get-Date -format yyyy.MM.dd).csv"
#Import the created csv.
$csv = import-csv $CsvRunFile
$results = foreach($csv_line in $csv) {
$ctag = $csv_line.ctag
$test_ping = test-connection $ctag -Count 1 -Quiet
#If the computer is pingable (IE: Online)
switch ($test_ping) {
$true {
#Pull the actual logged in user.
$Username = (Get-WmiObject -ComputerName $ctag -Class Win32_ComputerSystem).Username.Split("\\")[1]
#If the last modified folder is 'public' put an error, otherwise pull the username's information from AD.
#This was from when it pulled from the \User folder rather than the last log in, this is probably removeable.
if ($Username -eq "Public") {
$ADName = "No User"
} else {
$ADName = Get-ADUser -Identity $Username
$ADName = $ADName.Name
} #end If
}#end Switch:True
#Show there was an error when pinging the computer.
$false {$ADName = "ERROR"}
}#end Switch
#write the results the new output CSV.
$result = [PSCustomObject]#{
CTAG = $ctag
Username = $ADName
}#end PSCustom Object
$result
} #end foreach
#Turn the .txt into a CSV so it can be manually compared to the list in the original excel file.
$results | Export-Csv -path $output
There might be a leftover-value in $UserName for some reason or $ADName is still it's old value because you tried to run Get-ADUser -Identity $null when there was no user logged on (WMI returns $null when there's no user logged in).
I also changed your ping-test from a switch to an if-test just to cleanup the code. I've never seen Public being returned, but I left it as it doesn't really hurt either.
Try:
#If the computer is pingable (IE: Online)
if($test_ping) {
#Clear username var just to be safe
$Username = $null
#Pull the actual logged in user.
$Username = (Get-WmiObject -ComputerName $ctag -Class Win32_ComputerSystem).Username | ? { $_ } | % { $_.Split("\\")[1] }
#If the last modified folder is 'public' put an error, otherwise pull the username's information from AD.
#This was from when it pulled from the \User folder rather than the last log in, this is probably removeable.
if (($Username -eq "Public") -or ($Username -eq $null)) {
$ADName = "No User"
} else {
$ADName = Get-ADUser -Identity $Username
$ADName = $ADName.Name
} #end If username public
} else { $ADName = "ERROR"}

Script to add AD Group to Local Admin Group on multiple servers

##Roji P Rajan
$ErrorActionPreference = "silentlycontinue"
$Domain = Read-Host "`nEnter Domain name to connect"
$UserName = Read-Host "`nEnter AD Group name to add "
$DomName = $domain + "/" + $username
write-host "`n"
foreach($server in (gc .\servers.txt)){
$i= 0
$Boo= 0
if (Test-Connection $server -Count 1 -Quiet) {
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$members = #($group.psbase.Invoke("Members"))
$Check =($members | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -contains "$UserName"
If ($Check -eq $True) {
write-host "$server`t- Already Member" -foregroundcolor "yellow" }
else {
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$Group.Add("WinNT://" + $domain + "/" + $username)
$mem = ($Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}) `
-replace ('WinNT://DOMAIN/' + $server + '/'), '' -replace ('WinNT://DOMAIN/', 'DOMAIN\') -replace ('WinNT://', '')
$total = $mem.count
Foreach ($member in $mem) {
if ("$member" -eq "$Domain/$UserName"){
write-host "$server`t- Successfully Updated" -foregroundcolor "green"
$Boo = 1 }
$i=$i+1
If ($total -eq $i -And $Boo -eq 0) {
write-host "$server`t- Failed - User not exist or the server is not ready" -foregroundcolor "magenta" }
}
}
}
else {
write-host "$server `t- Failed to connect the Host Name" -foregroundcolor "Red" }
}
write-host "`n"
By using the above set of powershell code i am able to add a specific domain group to local administrative group in multiple servers. But if i run the script from any of the one server which is already in servers.txt, that specific server fails to update.. Can anybody guide me what i missed..
Thanks in advance..
Here's a link to a simple script that works:
https://deepakkhare.azurewebsites.net/powershell-add-remove-multiple-security-groups-on-multiple-windows-servers/
# This script will add multiple groups on multiple servers
# Make sure you have one server in each row in the servers text file
# you must have administrator access on the server
$ServersList = “D:\ServersList.txt”
$ServerNames = get-content $ServersList
$UserGroupFilePath = “D:\SecurityGroup.txt”
$UserGroupList = get-content $UserGroupFilePath
$DomainName =”Enter your domain name here”
foreach ($name in $ServerNames)
{
$localAdminGroup = [ADSI](“WinNT://$name/Administrators”)
# Add all the groups in text file to the current server
foreach ($UserGroupName in $UserGroupList)
{
$AdminsG = [ADSI] “WinNT://$DomainName/$UserGroupName”
$localAdminGroup.Add($AdminsG.PSBase.Path)
Write-Host “Adding” $AdminsG.PSBase.Path “to” $name
} # End of User Group Loop
} # End of Server List Loop
Remove multiple security groups on multiple servers
# This script will delete multiple security groups on multiple servers
# Make sure you have one server in each row in the servers text file
# you must have administrator access on the server
$ServersList = “D:\ServersList.txt”
$ServerNames = get-content $ServersList
$UserGroupFilePath = “D:\SecurityGroup.txt”
$UserGroupList = get-content $UserGroupFilePath
$DomainName =”Enter your domain name here”
foreach ($name in $ServerNames)
{
$localAdminGroup = [ADSI](“WinNT://$name/Administrators”)
# Add all the groups in text file to the current server
foreach ($UserGroupName in $UserGroupList)
{
$AdminsG = [ADSI] “WinNT://$DomainName/$UserGroupName”
$localAdminGroup.remove($AdminsG.PSBase.Path)
Write-Host “remove” $AdminsG.PSBase.Path “to” $name
} # End of User Group Loop
} # End of Server List Loop
Create a security group for your server administrators to the local group on all of the servers. E.g. "ServerAdmins#domain".
Use a GPO to Enforce and restrict local administrator group membership.
When it is determined that a certain role (RBA) needs ServerAdmin on all servers (your Windows Server Admins for example), add that group to the ServerAdmins group. E.g. Windows Admin Team. Put your administrators in the admins group.
When it is a smaller subset of servers that a RBA group needs access to, create a ExchangeServerAdmins group, add that to the GPO for those servers. The Exchange admins would be in a Exchange Admin Team group. Exchange Admin Team goes in the ExchangeServerAdmins group.
This way you control what groups have access to the servers by GPO, as chosen by Role Based Access. Control over the membership in the groups is controlled by storign those groups where the appropriate people can edit it. You can also delegate this by making team leads owners of their groups.
This also allows temporary rights. If you bring in a consultant who needs Exchange and Lync access, you add him to those teams. When he leaves you take him out. ONE edit coming, one going, both done at the group membership level, easy.
This also dramatically decreases maintenance when you start talking about Enterprise level administration. If you have 10-20, or even 200 servers, you can script out these changes, but what if you have 1000 or more servers?
I did a quick test by updating the local admin group of the server I was using. What is the error message? Can you remove the $ErrorActionPreference = "silentlycontinue" to see if an error is generated?
Here is the sample code:
#Set variables
$Domain = "Contoso"
$UserName = "JohnSmith"
$server = $env:COMPUTERNAME
#Get local admin group
$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)
$CurrentMembers = $Group.PSbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}
#Add user to local admin group
$Group.Add("WinNT://" + $domain + "/" + $username)
#verify add
$VerifyComputer = [ADSI](”WinNT://” + $server + “,computer”)
$VerifyGroup = $VerifyComputer.psbase.children.find(”Administrators”)
$VerifyMembers= $VerifyGroup.PSbase.Invoke("Members") | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}