I want to create a PowerShell script to extract a text file with a list of words specified in an Exchange Server 2016 transport rule. I'm stuck with handling the obtained list.
To get the list, I do this within Exchange Management Shell:
$SubjectOrBodyContainsWords = Get-TransportRule "My rule name" | Select-Object -Property SubjectOrBodyContainsWords
I verify that the list is correct using this:
$FormatEnumerationLimit = 10000
$SubjectOrBodyContainsWords | Format-Table -HideTableHeaders | Out-String -width 10000
The output looks like (Just an example, actual list is much much bigger):
{unsubscribe, mailing, blabla}
Now I want to iterate the list to do something with each item. I tried something like this (Just a simple example):
$I = 10;
foreach ($A in $SubjectOrBodyContainsWords)
{
$I++;
$I;
$A;
}
The problem is that it doesn't loop all the items. It looks like there is only one item.
What am I doing wrong?
Thanks.
I don't have access to my Exchange server at the moment but just try the following suggestions. Just ask for and expand the property.
(Get-TransportRule "My rule name").SubjectOrBodyContainsWords
# Or
Get-TransportRule "My rule name" |
Select-Object -ExpandProperty SubjectOrBodyContainsWords
Since this returns an array, you need to expand that listing.
Or you can do this to turn into a list to work with...
"{unsubscribe, mailing, blabla}" -replace '\s|{|}' -split ',' | foreach {
# Code to do something with each item
$PSItem }
# Results
unsubscribe
mailing
blabla
... and
Potential duplicate of this use case
Exchange Shell - SubjectOrBodyContainsWords
Related
I have a database that contains a log of domains listed in the following matter:
.youtube.com
.ziprecruiter.com
0.etsystatic.com
0.sparkpost.com
00.mail.ne1.yahoo.com
00072e01.pphosted.com
00111b01.pphosted.com
001d4f01.pphosted.com
011.mail.bf1.yahoo.com
1.amazonaws.com
How would I go about cleaning them up using powershell or grep, though I rather use powershell, so that they contain only the root domain with the .com extension and remove whatever word and . is before that.
I'm thinking best way to do is is a query that looks for dots from right to left and removes the second dot and whatever comes after it. For example 1.amazonaws.com here we remove the second dot from the right and whatever is after it?
i.e.
youtube.com
ziprecruiter.com
etsystatic.com
yahoo.com
pphosted.com
amazonaws.com
You can read each line into an array of strings with Get-Content, Split on "." using Split(), get the last two items with [-2,-1], then join the array back up using -join. We can then retrieve unique items using -Unique from Select-Object.
Get-Content -Path .\database_export.txt | ForEach-Object {
$_.Split('.')[-2,-1] -join '.'
} | Select-Object -Unique
Or using Select-Object -Last 2 to fetch the last two items, then piping to Join-String.
Get-Content -Path .\database_export.txt | ForEach-Object {
$_.Split('.') | Select-Object -Last 2 | Join-String -Separator '.'
} | Select-Object -Unique
Output:
youtube.com
ziprecruiter.com
etsystatic.com
sparkpost.com
yahoo.com
pphosted.com
amazonaws.com
You can use the String.Trim() method to clean leading and trailing dots, then use the regex -replace operator to remove everything but the top- and second-level domain name:
$strings = Get-Content database_export.txt
#($strings |ForEach-Object Trim '.') -replace '.*?(\w+\.\w+)$','$1' |Sort-Object -Unique
here is yet another method. [grin]
what it does ...
creates an array of strings to work with
when ready to do this for real, remove the entire #region/#endregion section and use Get-Content to load the file.
iterates thru the $InStuff collection of strings
splits the current item on the dots
grabs the last two items in the resulting array
joins them with a dot
outputs the new string to the $Results collection
shows that on screen
the code ...
#region >>> fake reading in a text file
# in real life, use Get-Content
$InStuff = #'
.youtube.com
.ziprecruiter.com
0.etsystatic.com
0.sparkpost.com
00.mail.ne1.yahoo.com
00072e01.pphosted.com
00111b01.pphosted.com
001d4f01.pphosted.com
011.mail.bf1.yahoo.com
1.amazonaws.com
'# -split [System.Environment]::NewLine
#endregion >>> fake reading in a text file
$Results = foreach ($IS_Item in $InStuff)
{
$IS_Item.Split('.')[-2, -1] -join '.'
}
$Results
output ...
youtube.com
ziprecruiter.com
etsystatic.com
sparkpost.com
yahoo.com
pphosted.com
pphosted.com
pphosted.com
yahoo.com
amazonaws.com
please note that this code expects the strings to be more-or-less-valid URLs. i can think of invalid ones that end with a dot ... and those would fail. if you need to deal with such, add the needed validation code.
another idea ... if the file is large [tens of thousands of strings], you may want to use the ForEach-Object pipeline cmdlet [as shown by RoadRunner] to save RAM at the expense of speed.
Here is my scenario,
I wrote a SQL query that extracts user accounts with a null username from our student information system. Lets just assume these are newly enrolled students. I then want to take this list, which has a column of suggested usernames, done by simple concatenation in the SQL query. I want to loop through that csv and check to make sure that username doesn't already exist in AD , if it does, append the next available number to the username.
So in my test environment I have a csv that looks like this. ( I made this up for testing)
StudentID,First,Last,SuggestedUsername
12345,tony,Test,testto
54321,tolly,test,testto
I my test AD environment I already have a student named Tommy Test or Testto, so in this case, my powershell script should tell me Tony Test should be testto1 and Tolly Test should be testto2. Is this making sense?
The meat of my script works, It will read the csv, loop through AD and return testto1 for line 1 of the csv, the problem is it will not read line 2, the script ends
I have been playing around with the arrays in the script but here is what I have so far
Import-module Activedirectory
Add-Pssnapin Quest.ActiveRoles.admanagement
$useraccounts =#()
$useraccounts = import-Csv "Path\Test.csv"
$userbase = Get-QADuser -sizelimit 0 -SearchRoot 'mydomain.com/OU'
foreach ($user in $useraccounts) {
if ($userbase)
{
$userbase = $userbase | Select samaccountname | %{($_ -split "#")[0]}
$UserNumbers = #()
$userbase | % {
if ($_ -Match '\d+')
{
$UserNumbers += $matches[0]
}
}
$MaxUserNumber = ($userNumbers | Measure-Object -max).Maximum
$suggestedUserName = $user+($MaxUserNumber+1)
}
Else
{
$SuggestedUserName = $user
}
}
Write-Host $suggestedUserName
Ok, your loop doesn't appear to be cycling because you aren't using an array of strings as your output, you are just using a string, so it just shows up with the last loop. But if you like my solution that's neither here nor there, because I think I have a better option for you. Just for the sake of simplicity, back up your CSV and then delete the Suggested Name column and run this against that.
$Users = Import-CSV "path\test.csv"
$Users|%{$_|Add-Member UserID ("$($_.last)$($_.first.substring(0,2))")}
$NameConflicts = $Users|group userid|?{$_.count -gt 1}
ForEach($Name in $NameConflicts){
$x=0
if(dsquery user -samid "$($name.name)*"){
$x = Get-ADUser -Filter {samaccountname -like "$($Name.Name)*"}|%{$_.samaccountname -replace "($($Name.name))(\d*)","`$2"}|sort -Descending |select -first 1
}
For($i=if($x -gt 0){0}else{1};$i -lt ($Name.count);$i++){
$Name.Group[$i].UserID = "$($Name.Name)$($i+$x)"
}
}
$Users|group userid|?{$_.count -eq 1}|%{if(dsquery user -samid "$($_.name)"){$_.Group[0].UserID = "$($_.Name)1"}}
$Users|FT -auto
That loads your list, creates potential user names by taking the last name and the first two letters of the first name. Then it groups by that, and finds any of them that have more than one. Then for each of those it checks AD for any existing accounts with names like that, and takes the number off the end of the name, and selects the highest one. Then if it found any already in existence it renames all of the potential user names to append the next available number to the end (if none are found in AD it leaves the first one, and renames the rest of them). Lastly it checks all of the other names, where there is just one user name, and if it is in AD already it adds a 1 to the end of the user id.
This should run faster than your script, because I'm only searching for names that are needed to be checked, and it's filtering at the provider level instead of taking all names, and then filtering through powershell.
I'm looking for a solution for the following challenge:
I run the following command in PowerShell.
Import-Module servermanager
Get-WindowsFeature | where {$_.Installed -eq "True"} | ft DisplayName, Installed > output.txt
Now I want to add a character at the end of each row. How can I do that?
I think I have to add the content into an array, but I don't know how to finish the code.
At the end I have to load the content into the EventViewer. If I send it directly, the event description isn't formatted well.
You could add another field to the records like this:
Get-WindowsFeature | ? { $_.Installed } |
select DisplayName, Installed, #{n='Other',e={'c'}} | ft
It sounds like instead of using ft > output.txt, you want something like:
foreach { echo ( $_.DisplayName + " " + $_.Installed + " extra_stuff" ) } > output.txt
It doesn't give you a nicely formatted table though.
It's a little outside the scope of what you directly asked, but I'd suggest skipping the 'write to text file' stage and pass directly to the destination.
Import-Module servermanager
$installedFeatures = #() # Creates a blank array for the features
$output = #() # Creates a blank array for formatted results
$yourChar # The character/ string you want to add at the end of each row
$installedFeatures = Get-WindowsFeature | Where-Object {$_.Installed -eq "True"}
foreach($feature in $installedFeatures)
{
$output += "$($feature.displayName) $($feature.installed) $yourChar"
}
Once you've iterated through all the Windows features, your $output variable will have an array of strings in the format of displayName installed $yourChar. You can then write to disk, or send the object somewhere else (this is the beauty of PowerShell objects!).
I'm trying to write this PS script but if someone beats me to the punch I'm sure they will get free karma.
Anyway here is what I want to take a file setup like this
foo.bar=Some random text is stored here
foo.bar=Lazy maintainers make me angry
bar.foo=Hello World!
bar.foo=Hello World!
The main goal is to remove any duplicated entries, which I have several of . . . This seems easy enough with
Get-Content c:\list.txt | Select-Object -Unique
But I would also like to store any conflicts that have the same key identifiers into a separate file for so I can review which ones I should keep.
I'm still a PS novice and haven't found a good way to do this yet.
You can use Group-Object to group together items with the same key. Then look for groups with more than one element in them (indicating duplicate entries). Finally, print those out to a file somewhere:
# raw content
$lines = Get-Content C:\data.txt
# package each line into a little object with properties Key and Val
$data = $lines |%{ $key,$val = $_.Split('='); new-object psobject -prop #{Key = $key; Val = $val} }
# group the objects by key, only keep groups with more than 1 element
$duplicates = $data | group Key |?{$_.Count -gt 1}
# print out each key and the different values it has been given
$duplicates |%{ "--- [$($_.Name)] ---"; $_.Group | select -expand Val }
Result:
--- [foo.bar] ---
Some random text is stored here
Lazy maintainers make me angry
--- [bar.foo] ---
Hello World!
Hello World!
You can pipe that to Out-File if you want to store in a log.
In a PowerShell script, I have some objects that I pass to the Format-Table CmdLet.
The output of my script looks like this:
Something...
Operation AttributeName AttributeValue
--------- ------------- --------------
Delete Member John Doe
Something else...
Since the meaning of the fields is pretty self-explanatory, I would like to remove the headers, the '---' separators and the blank lines at the beginning and at the end from the output of Format-Table.
I don't think that the CmdLet supports this (or at least if there's a parameter to do this I couldn't find it).
What would the best way to leave only the lines with the actual values from the output of Format-Table?
Try the -HideTableHeaders parameter to Format-Table:
gci | ft -HideTableHeaders
(I'm using PowerShell v2. I don't know if this was in v1.)
Try -ExpandProperty. For example, I use this for sending the clean variable to Out-Gridview -PassThru , otherwise the variable has the header info stored. Note that these aren't great if you want to return more than one property.
An example:
Get-ADUser -filter * | select name -expandproperty name
Alternatively, you could do this:
(Get-ADUser -filter * ).name
The -HideTableHeaders parameter unfortunately still causes the empty lines to be printed (and table headers appearently are still considered for column width). The only way I know that could reliably work here would be to format the output yourself:
| % { '{0,10} {1,20} {2,20}' -f $_.Operation,$_.AttributeName,$_.AttributeValue }
Here is how I solve this. I just pipe the output to Out-String and then pass that output to the .NET Trim function:
(gci | ft -HideTableHeaders | Out-String).Trim()
This will strip out the line breaks before and after the table.
You can also use TrimStart to just take care of the header's line break if you still want the trailing line breaks.
(gci | ft -HideTableHeaders | Out-String).TrimStart()
Another approach is to use ForEach-Object to project individual items to a string and then use the Out-String CmdLet to project the final results to a string or string array:
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String
#Result: One multi-line string equal to:
#"
CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0
CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d
CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b
CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426
"#
gci Microsoft.PowerShell.Core\Registry::HKEY_CLASSES_ROOT\CID | foreach { "CID Key {0}" -f $_.Name } | Out-String -Stream
#Result: An array of single line strings equal to:
#(
"CID Key HKEY_CLASSES_ROOT\CID\2a621c8a-7d4b-4d7b-ad60-a957fd70b0d0",
"CID Key HKEY_CLASSES_ROOT\CID\2ec6f5b2-8cdc-461e-9157-ffa84c11ba7d",
"CID Key HKEY_CLASSES_ROOT\CID\5da2ceaf-bc35-46e0-aabd-bd826023359b",
"CID Key HKEY_CLASSES_ROOT\CID\d13ad82e-d4fb-495f-9b78-01d2946e6426")
The benefit of this approach is that you can store the result to a variable and it will NOT have any empty lines.
I know it's 2 years late, but these answers helped me to formulate a filter function to output objects and trim the resulting strings. Since I have to format everything into a string in my final solution I went about things a little differently.
Long-hand, my problem is very similar, and looks a bit like this
$verbosepreference="Continue"
write-verbose (ls | ft | out-string) # this generated too many blank lines
Here is my example:
ls | Out-Verbose # out-verbose formats the (pipelined) object(s) and then trims blanks
My Out-Verbose function looks like this:
filter Out-Verbose{
Param([parameter(valuefrompipeline=$true)][PSObject[]]$InputObject,
[scriptblock]$script={write-verbose "$_"})
Begin {
$val=#()
}
Process {
$val += $inputobject
}
End {
$val | ft -autosize -wrap|out-string |%{$_.split("`r`n")} |?{$_.length} |%{$script.Invoke()}
}
}
Note1: This solution will not scale to like millions of objects(it does not handle the pipeline serially)
Note2: You can still add a -noheaddings option.
If you are wondering why I used a scriptblock here, that's to allow overloading like to send to disk-file or other output streams.