Remove users from application using LithnetRMA - powershell

Im using LithnetRMA to update MIM application membership.
I have multiple users who are linked to an application (ObjectID: 168b8077-052c-489d-b4c0-1700ff817d1f)
I would like to remove several users who have left the company. Not sure if im on the right path?
# Load the Lithnet Service PowerShell module
Import-Module LithnetRMA
$userList = #"
ObjectID;
139b8058-052c-459d-b3c0-1600fo417d1a
195b8057-042c-439d-b2c0-1800fp117d1b
169b8047-022c-449d-b0c0-1900fi217d1c
162b8037-012c-489d-b1c0-1000fu417d1d
164b8027-002c-488d-b8c0-1300fy317d1e
163b8017-012c-449d-b7c0-1500fr517d1f
"#
#Remove users from the application
foreach($item in $userList) {
#Get the users and application objects
$user = Get-Resource -ID $item.ObjectID
$application = Get-Resource -ID "168b8077-052c-489d-b4c0-1700ff817d1f"
$application.ExplicitMember.Remove($item.ObjectID)
Save-Resource -Resources $application
}

I can't speak to the LithnetRMA part, but it looks like your intent is to define $userList as an array of objects that have an .ObjectId property, but in reality you're simply assigning a single, multiline string.
Therefore, change your assignment as follows:
# Parse the string into objects that have an .ObjectID property.
$userList = ConvertFrom-Csv #"
ObjectID
139b8058-052c-459d-b3c0-1600fo417d1a
195b8057-042c-439d-b2c0-1800fp117d1b
169b8047-022c-449d-b0c0-1900fi217d1c
162b8037-012c-489d-b1c0-1000fu417d1d
164b8027-002c-488d-b8c0-1300fy317d1e
163b8017-012c-449d-b7c0-1500fr517d1f
"#
ConvertFrom-Csv is used to parse the multiline string into the objects of interest, by interpreting the string as single-column CSV data.
Note that the trailing ; was removed from the ObjectID line so that the ; doesn't become part of the property name. (It would be ignored if you used ConvertFrom-Csv -Delimiter ';', however.)

Related

Powershell - How to use an * as a String Filter

I need to filter out only the AD groups from the Net User command in powershell. All the AD groups begin with an * so i would like to filter out the string by displaying everything that's preceeded by an *
I get an error since '*' is a special character and cannot be used. How do i get powershell to ignore it as a special character?
I cannot use any other commands to get AD groups, so Get-AD is not an option. i only have Net user to work with.
My base script,
Net User USER /domain | Select-String '*'
I cannot use any other script than Net user to accomplish this task, even though Get-AD would be simpler, i do not have the option.
Santiago's helpful answer shows a more robust, OO solution that is much more in the spirit of PowerShell.
To answer your question as asked:
Select-String by default interprets its (positionally implied) -Pattern argument as a regex (regular expression), where * is a metacharacter.
While \-escaping regex metacharacters is possible (and is necessary in the event that you need more sophisticated matching that requires a regex), the direct solution is to add the -SimpleMatch switch, which causes the -Pattern argument to be interpreted as a literal (verbatim) string:
net user $someuser /domain | Select-String * -SimpleMatch
Also note that what Select-String outputs by default aren't just the matching input lines as-is, but Microsoft.PowerShell.Commands.MatchInfo objects that provide metadata for each match, with the matching line text stored in the .Line property.
While that distinction doesn't matter much for displaying results, it may for programmatic processing, so if you only want to output the text of the matching lines, add -Raw in PowerShell (Core) 7+, or pipe to | ForEach-Object Line in Windows PowerShell.
The above will show those net user output lines that contain a literal *, and therefore all group memberships, which is good enough for the human observer.
You indeed need regex matching and operations if you want to extract the group names individually, for later programmatic processing:
# Use an ordered hashtable to collect the group names in,
# with keys 'Local' and 'Global', targeting the *current* user in this example.
$groupMemberships = [ordered] #{}
(((net user $env:USERNAME) -join "`n") -split "`n`n")[-1] -split '\n' -match ' \*' |
ForEach-Object {
$tokens = $_ -split ' \*'
if ($tokens[0] -notmatch '^ ') {
$key = if ($groupMemberships.Count -eq 0) { 'Local' } else { 'Global' }
}
$groupMemberships[$key] += #($tokens[1..($tokens.Count-1)].Trim())
}
$groupMemberships # display result.
Sample output:
Name Value
---- -----
Local { Administrators }
Global { Department1, Region1 }
That is $groupMemberships.Local $groupMemberships.Global then contains the name(s) of the local / global (AD) groups the user is a member of, respectively, as an array.
Note:
The solution above is complex, because it tries to be as robust as possible.
Notably, it is possible - albeit not likely in practice - that output lines that are unrelated to group names contain * as well, notably the Comment and User's comment fields.
Therefore, only the last paragraph of net user's output is considered, which is known to contain the group names - note that matching lines by field-name parts such as Local and Global is explicitly avoided, as the field names are localized based on your system's display language.
The last paragraph is known to list the local group memberships first, followed by the global (AD) ones. Each line in the last paragraph can contain multiple (*-prefixed) group names and there can be overflow lines for additional groups that don't fit on the first line for the given scope. Such overflow flow lines can be detected by starting with whitespace.
Instead of trying to parse the output from net user USER /domain I would use what's already available in powershell. You can get the current logged on user's Active Directory Group Membership using adsi and adsisearcher.
Here are 2 different ways of accomplishing it.
By querying the user's memberof attribute:
$searcher = [adsisearcher]::new(
[adsi] "LDAP://$env:USERDNSDOMAIN",
[string] "(cn=$env:USERNAME)",
[string[]] ("memberOf", "cn")
)
$searcher.FindOne().Properties['memberof'] | ForEach-Object {
$searcher.Filter = "(distinguishedName=$_)"
$searcher.FindOne().Properties['cn'][0]
}
By querying all groups having the user as a member:
$searcher = [adsisearcher]::new(
[adsi] "LDAP://$env:USERDNSDOMAIN",
[string] "(cn=$env:USERNAME)",
[string[]] ("distinguishedName", "cn")
)
$userDn = $searcher.FindOne().Properties['distinguishedName'][0]
$searcher.Filter = "(&(objectCategory=group)(member=$userDn))"
$searcher.FindAll() | ForEach-Object {
$_.Properties['cn'][0]
}
You can use a backslash to escape regex special characters and use ^ to specify start of string:
> #("a", "*b", "c*", "*d", "e**") | Select-String -Pattern '^\*'
*b
*d
So, to display the groups you could use, for example:
Net User USER /domain | % { $_ -split "\s+" -match "^\*" }
As per the comment, if the group names may contain spaces then obviously splitting on space characters would be inappropiate.
An alternative:
Net User USER /domain | % { $_ -split '^[^*]+\*?' -match '.+' }
Or, if we only want to look at the lines beginning "Local Group Memberships" or "Global Group Memberships" we could use, for example:
Net User USER /domain |
? { $_ -match '^(?:Local|Global) Group Memberships +\*(.+)' } | % { $matches[1] }

Get-GPOReport and Search For Matched Name Value

I'm trying to use the PowerShell command 'Get-GPOReport' to get GPO information in XML string format so I can search it for sub-Element values with unknown and different Element tag names (I don't think XML Object format will work for me, so I didn't perform a cast with "[xml]"), but I haven't been able to parse the XML output so that I can grab the line or two after a desired "Name" Element line that matches the text I'm searching for.
After, I have been trying to use 'Select-String' or 'Select-XML' with XPath (formatting is unclear and I don't know if I can use a format for various policy record locations) to match text and grab a value, but I haven't had any luck.
Also, if anyone know how to search for GPMC GUI names (i.e. "Enforce password history") instead of needing to first locate back-end equivalent names to search for (i.e. "PasswordHistorySize"), that would also be more helpful.
The following initial code is the part that works:
$String = "PasswordHistorySize" # This is an example string, as I will search for various strings eventually from a file, but I'm not sure if I could search for equivalent Group Policy GUI text "Enforce password history", if anyone knows how to do that.
$CurrentGPOReport = Get-GPOReport -Guid $GPO.Id -ReportType Xml -Domain $Domain -Server $NearestDC
If ($CurrentGPOReport -match $String)
{
Write-Host "Policy Found: ""$($String)""" -Foregroundcolor Green
#
#
# The following code is what I've tried to use to get value data, without any luck:
#
$ValueLine1 = $($CurrentGPOReport | Select-String -Pattern $String -Context 0,2)
$Value = $($Pattern = ">(.*?)</" ; [regex]::match($ValueLine1, $Pattern).Groups[1].Value)
}
I've been looking at this since yesterday and didn't understand why Select-String wasn't working, and I figured it out today... The report is stored as a multi-line string, rather than an array of strings. You could do a -match against it for the value, but Select-String doesn't like the multi-line formatting it seems. If you -split '[\r\n]+' on it you can get Select-String to find your string.
If you want to use RegEx to just snipe the setting value you can do it with a multi-line regex search like this:
$String = "PasswordHistorySize" # This is an example string, as I will search for various strings eventually from a file, but I'm not sure if I could search for equivalent Group Policy GUI text "Enforce password history", if anyone knows how to do that.
$CurrentGPOReport = Get-GPOReport -Guid $GPO.Id -ReportType Xml -Domain $Domain -Server $NearestDC
$RegEx = '(?s)' + [RegEx]::Escape($String) + '.+?Setting.*?>(.*?)<'
If($CurrentGPOReport -match $RegEx)
{
Write-Host "Policy Found: ""$String""" -Foregroundcolor Green
$Value = $Matches[1]
}
I'm not sure how to match the GPMC name, sorry about that, but this should get you closer to your goals.
Edit: To try and get every setting separated out into it's own chunk of text and not just work on that one policy I had to alter my RegEx a bit. This one's a little more messy with the output, but can be cleaned up simply enough I think. This will split a GPO into individual settings:
$Policies = $CurrentGPOReport -split '(\<(q\d+:.+?>).+?\<(?:\/\2))' | Where { $_ -match ':Name' }
That will get you a collection of things that look like this:
<q1:Account>
<q1:Name>PasswordHistorySize</q1:Name>
<q1:SettingNumber>21</q1:SettingNumber>
<q1:Type>Password</q1:Type>
</q1:Account>
From there you just have to filter for whatever setting you're looking for.
I have tried this with XPath, as you'll have more control navigating in the XML nodes:
[string]$SearchQuery = "user"
[xml]$Xml = Get-GPOReport -Name "Default Domain Policy" -ReportType xml
[array]$Nodes = Select-Xml -Xml $Xml -Namespace #{gpo="http://www.microsoft.com/GroupPolicy/Settings"} -XPath "//*"
$Nodes | Where-Object -FilterScript {$_.Node.'#text' -match $SearchQuery} | ForEach-Object -Process {
$_.Name #Name of the found node
$_.Node.'#text' #text in between the tags
$_.Node.ParentNode.ChildNodes.LocalName #other nodes on the same level
}
After testing we found that in the XML output of the Get-GPOReport cmdlet, the setting names does not always match that of the HTML output. For example: "Log on as a service" is found as "SeServiceLogonRight" in the XML output.

Iterate through XML nodes / objects and pass to variables in Powershell

I'd be wanting to iterate through a set of XML and then pass those to variables which can be printed.
Here is an example of the data:
<applications>
<size>75</size>
<application>
<name>Applications 1</name>
<path>/Applications/Utilities/Application 1</path>
<version>10.14</version>
</application>
<application>
<name>Application 2</name>
<path>/Applications/Utilities/Application 2</path>
<version>6.3.9</version>
</application>
</applications
I've looked at using ForEach-Object when trying to output it but to no avail.
[string]$applicationProperties = $API.applications.application| ForEach-Object {
$_.name
$_.path
$_.version
}
This works but puts them all on one line, I'd like them so they print on individual lines but I couldn't prefix the $_ variable. I'm new to POSH as you can tell.
e.g. so I'd like to have name/path/version data saved to variables
[string]$applicationProperties = $API.applications.application | ForEach-Object {
[string]$name_var = $_.name
[string]$path_var = $_.path
[string]$version_var = $_.variable
}
This gives me one "application", but not all the possible objects. Also mentions that even when I'm putting down $name_var it's not accessing that variable? Do I need to do something to access that variable?
Any advice would be appreciated.
When you assign the output from ForEach-Object to [string]$applicationProperties, you're forcing PowerShell to convert all the strings into a single string because of the cast to [string].
What you'll want to do is create a new object for each application node that you're iterating over:
$appInformation = $API.applications.application | ForEach-Object {
# Create a new custom objects with the values from the XML nodes
[pscustomobject]#{
Name = $_.name
Path = $_.path
Version = $_.version
}
}
Now $appInformation will contain an array of objects each with a Name, Path and Version property. You can then further use and/or manipulate these objects in your scripts rather than just having a bunch of strings:
$appInformation |ForEach-Object {
Write-Host "Here is the version of application '$($_.Name)': $($_.Version)"
}
If you want to see them printed in the console with each property value on a separate line just pipe the array to Format-List:
$appInformation |Format-List

Read a CSV in powershell with a variable number of columns

I have a CSV that contains a username, and then one or more values for the rest of the record. There are no headers in the file.
joe.user,Accounting-SG,CustomerService-SG,MidwestRegion-SG
frank.user,Accounting-SG,EastRegion-SG
I would like to read the file into a powershell object where the Username property is set to the first column, and the Membership property is set to either the remainder of the row (including the commas) or ideally, an array of strings with each element containing a single membership value.
Unfortunately, the following line only grabs the first membership and ignores the rest of the line.
$memberships = Import-Csv -Path C:\temp\values.csv -Header "username", "membership"
#{username=joe.user; membership=Accounting-SG}
#{username=frank.user; membership=Accounting-SG}
I'm looking for either of these outputs:
#{username=joe.user; membership=Accounting-SG,CustomerService-SG,MidwestRegion-SG}
#{username=frank.user; membership=Accounting-SG,EastRegion-SG}
or
#{username=joe.user; membership=string[]}
#{username=frank.user; membership=string[]}
I've been able to get the first result by enclosing the "rest" of the data in the csv file in quotes, but that doesn't really feel like the best answer:
joe.user,"Accounting-SG,CustomerService-SG,MidwestRegion-SG"
Well, the issue is that what you have isn't really a (proper) CSV. The CSV format doesn't support that notation.
You can "roll your own" and just process the file yourself, something like this:
$memberships = Get-Content -LiteralPath C:\temp\values.csv |
ForEach-Object -Process {
$user,$membership = $_.Split(',')
New-Object -TypeName PSObject -Property #{
username = $user
membership = $membership
}
}
You could do a half and half sort of thing. Using your modification, where the groups are all a single field in quotes, do this:
$memberships = Import-Csv -Path C:\temp\values.csv -Header "username", "membership" |
ForEach-Object -Process {
$_.membership = $_.membership.Split(',')
$_
}
The first example just reads the file line by line, splits on commas, then creates a new object with the properties you want.
The second example uses Import-Csv to create the object initially, then just resets the .membership property (it starts as a string, and we split the string so it's now an array).
The second way only makes sense if whatever is creating the "CSV" can create it that way in the first place. If you have to modify it yourself every time, just skip this and process it as it is.

Line break issue when configuring "send on behalf of"

I have a script to set send on behalf of permissions in Exchange Management Shell, but when you try and use it it fails because the output of the first part is too long and truncates over 2 lines.
First thing we do is build our array from lists of people and put them into some variables to pass:
function Add-Send ($mailbox, $target) {
#"Granting send on behalf for $mailbox to $target"
Set-Mailbox -Identity $mailbox -GrantSendOnBehalfTo #{ Add = $target }
}
We pass a long list as the $target and the maibox name is $mailbox and if we output the text we get:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson" }
All fine and good but if there are more than N characters in the output then we get a line break:
Set-Mailbox -Identity "mr.jeff" -GrantSendOnBehalfTo #{ Add = "alan.alanson", "bob.bobson", "steve.stevenson", ...
..., "cath.cathdotir" }
When you run this script with the overlength output, then command fails as the output which should be passed to the CLI is passed over more than one line. PowerShell treats each line as a separate command, and they obviously fail with bad syntax.
Our string is output from an array that we build like this:
function Send-Array ($mailbox) {
$target = Get-Content ".\list\dpt1.txt"
$target += Get-Content ".\list\$mailbox.txt"
$target += Get-Content ".\list\dpt2.txt"
$target = $target | Select-Object -Unique
$separator = '", "'
$target= $target -replace '^|$','"' -join ','
Add-Send $mailbox $target
}
This gives us an array with strings that look like:
"alan.alanson", "bob.bobson", "steve.stevenson"
From here I am at a loss any ideas would be much appreciated.
The obvious solution would be to pass the names one at a time, but due to a gotcha with Exchange Server every time you set send on behalf of permissions with PowerShell it wipes the existing permissions, so you only end up with he last person granted permissions being able to send on behalf of.
See this link for help with your underlying issue.
Very basically, you will have to:
get the DistinguishedName of the user you need to add
store the current value of GrantSendOnBehalfTo in a variable
append the new user's distinguished name to the list
replace GrantSendOnBehalfTo with the new list
Afterwards you should not need to pass endless strings to the EMS (I hope so).