Powershell: How to obtain the complete list of distribution groups - powershell

I am completely new to powershell, I have never touched this scripting language before. However, I have some backgrounds in perl and bash scripting. I am trying to implement a small script that will obtain the list of DG in Exchange server, filters the results to get only the groups that have a certain string, corresponding to the current year.
Example: check the year, in this case 2011.
Filter Name Contains 'P11'
Return only the last DG name and parse the first 7 characters.
How could I do this using powershell from an exchange server? Here is what I got:
add-pssnapin Microsoft.Exchange.Management.PowerShell.E2010
# Retrieve all DGs
$temp = Get-DistributionGroup -ResultSize Unlimited |
foreach($group in $temp)
{
write-output "GroupName:$group "
Write-output "GroupMembers:"
Get-DistributionGroupMember $group |ft displayname,alias,primarysmtpaddress
write-output ‘ ‘
}
this results in the following error:
Unexpected token 'in' in expression or statement. At
C:\Users\jfb\Desktop\NewGroupProject.ps1:7 char:18
+ foreach($group in <<<< $temp)
+ CategoryInfo : ParserError: (in:String) [],
ParseException
+ FullyQualifiedErrorId : UnexpectedToken

Remove the trailing | in the line $temp = Get-DistributionGroup -ResultSize Unlimited | and it should work fine.
What is happening is that since you had the | it is treating the foreach as a foreach-object

Try this (not tested). Create a date object,using Get-Date, and format the date to include the last two digits of the year enclosed in asterisks. This would be the wildcard for the Get-DistributionGroup cmdlet. Select the last DG object and expand its name.
$name = Get-Date -Format *Pyy*
$group = Get-DistributionGroup $name | Select-Object -Last 1 -ExpandProperty Name
if($group)
{
$group.Substring(0,7)
}

Related

Where-Object Error When Passing Get-Content as Variable

First, my PS knowledge is very basic, so know that up front.
I'm working on a basic script to search EventIDs in archived .evtx files and kick out "reports". The Where-Object queries are in .txt files stored in .\AuditEvents\ folder. I'm trying to do a ForEach on the .txt files and pass each query to Get-WinEvent.
Here's an example of how the queries appear in the .txt files:
{($_.ID -eq "11")}
The script is:
$ae = Get-ChildItem .\AuditEvents\
ForEach ($f in $ae) {
$qs = Get-Content -Path .\AuditEvents\$f
Get-WinEvent -Path .\AuditReview\*.evtx -MaxEvents 500 | Select-Object TimeCreated, ID, LogName, MachineName, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-GridView -Title $f.Name
}
This is the error:
Where-Object : Cannot bind argument to parameter 'FilterScript' because it is null.
At C:\Users\######\Desktop\PSAuditReduction\PSAuditReduction.ps1:6 char:177
+ ... e, ProviderName, LevelDisplayName, Message | Where-Object $qs | Out-G ...
+ ~~~
+ CategoryInfo : InvalidData: (:) [Where-Object], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.WhereObjectCommand
Your symptom implies that $qs is $null, which in turn implies that file .\AuditEvents\$f is empty.
However, even if it had content, you couldn't pass the resulting string as-is to the (positionally implied) -FilterScript parameter of Where-Object requires a script block ({ ... }).
You must create a script block from the string explicitly, using [scriptblock]::Create().
A simplified example:
# Simulated input using a literal string instead of file input via Get-Content
$qs = '{ 0 -eq $_ % 2 }' # Sample filter: return $true for even numbers.
# Remove the enclosing { and }, as they are NOT part of the code itself
# (they are only needed to define script-block *literals* in source code).
# NOTE: If you control the query files, you can simplify them
# by omitting { and } to begin with, which makes this
# -replace operation unnecessary.
$qs = $qs.Trim() -replace '^\{(.+)\}$', '$1'
# Construct a script block from the string and pass it to Where-Object
1..4 | Where-Object ([scriptblock]::Create($qs)) # -> 2, 4
Note:
Your code assumes that each .\AuditEvents\$f file contains just one line, and that that line contains valid PowerShell source code suitable for use a Where-Object filter.
Generally, be sure to only load strings that you'll execute as code from sources you trust.
Taking a step back:
As Abraham Zinala points out, a much faster way to filter event-log entries is by using Get-WinEvent's -FilterHashtable parameter.
This allows you to save hastable literals in your query files, which you can read directly into a hashtable with Import-PowerShellDataFile:
# Create a file with a sample filter.
'#{Path=".\AuditEvents\.*evtx";ID=11}' > sample.txt
# Read the file into a hashtable...
$hash = Import-PowerShellDataFile sample.txt
# ... and pass it to Get-WinEvent
Get-WinEvent -MaxEvents 500 -FilterHashtable $hash | ...

I need to pass a line number from an object

>$search="<table id="
$linenumber= Get-Content ".\145039.html" | select-string $search | Select-Object LineNumber
$search="</table>"
$linenumber2= Get-Content ".\145039.html" | select-string $search | Select-Object LineNumber
#$linenumber2
# the list of line numbers to fetch
$linesToFetch = $linenumber[2]..$linenumber2[2]
$currentLine = 1
$result = switch -File ".\145039.html" {
default { if ($linesToFetch -contains $currentLine++) { $_ }}
}
# write to file and also display on screen by using -PassThru
$result | Set-Content -Path ".\excerpt.html" -PassThru
Cannot convert the "#{LineNumber=6189}" value of type "Selected.Microsoft.PowerShell.Commands.MatchInfo" to type "System.Int32".
At line:10 char:1
+ $linesToFetch = $linenumber[2]..$linenumber2[2]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
$linenumber and $linenumber2 return values like below but I just need to get the number not the column header.
LineNumber
----------
6015
Also, the final version needs to loop through all the html files in a directory not just one static file.
Sorry, there is probably a better way to do this but not sure how.
Thanks in advance!
Did a lot of googling but could not find the right solution.
Updated code:
$search1="disconnect-status"
$linenumber1= Get-Content ".\145039.html" | select-string
$search1
| Select-Object -ExpandProperty LineNumber
$search2="</table>"
$linenumber2= Get-Content ".\145039.html" | select-string
$search2 | Select-Object -ExpandProperty LineNumber
# the list of line numbers to fetch
$linesToFetch = $linenumber1[3]..$linenumber2[1]
$currentLine = 1
$result = switch -File ".\145039.html" {
default { if ($linesToFetch -contains $currentLine++) { $_ }}
}
# write to file and also display on screen by using -PassThru
$result | Set-Content -Path ".\excerpt.html" -PassThru
_____________________________________________________________
Thank you # mklement0
This now works for one file at a time now I need it go select text from all the HTML files in the directory.
Your immediate problem is that you need to change Select-Object LineNumber (which, due to positional parameter binding, is equivalent to Select-Object -Property LineNumber) to Select-Object -ExpandProperty LineNumber.
That is, you must use Select-Object's -ExpandProperty parameter in order to only get the values of the input objects' .LineNumber properties - see this post.
That said, your approach can be optimized in a number of ways, allowing you to make do with only a switch statement:
$output = $false; $openTagCount = 0
$result =
switch -File .\145039.html {
'<table id=' {
if (++$openTagCount -eq 3) { $output = $true } # 3rd block found
continue
}
'</table>' {
if ($output) { break } # end of 3rd block -> exit
continue
}
default {
if ($output) { $_ } # inside 3rd block -> output line
}
}
Note: This extracts the lines inside the third <table> element that has an id attribute, as implied by your original solution attempt; the solution you later edited into the question works differently.
Taking a step back:
It looks like your input is HTML, so you're usually better off using HTML parsing to handle your input:
In Windows PowerShell you may be able to use Invoke-WebRequest relying on the Internet Explorer engine if present (it isn't anymore by default in recent Windows versions).
In recent versions of Windows and in PowerShell (Core) (v6+), you'll either need New-Object -Com HTMLFile - see this answer - or a third-party solution such as such as the PowerHTML module that wraps the HTML Agility Pack - see this answer.

Trouble with $arrayList.Remove($item)

I am having trouble with this.
What i am trying to do is I have 2 different array, and I am trying to exclude $items in $ignore from $users
I can't use where $users | where {$ignore -notcontains $_} because the string in the $ignore array may contain wildcard (e.g., admin*)
But here is the main problem, when I matched the $item that i want to remove, it removes my entire array instead of the selected value.
[System.Collections.ArrayList]$users = Get-ADUser -Filter * | Select Name
$users = $users.name
$ignore = $audArr[5] -Split(",")
foreach ($item in $ignore) {
if ($item.Contains("*")) {
foreach ($user in $users) {
$matchResult = $user -like $item
if ($matchResult -eq $true){
$users = $users.Remove($item)
}
}
}
else {
$users = $users.Remove($item)
}
}
However, I if i try to .Add it works despite showing error
Cannot convert the "3" value of type "System.Int32" to type "System.Collections.ArrayList".
At C:\users\administrator\desktop\scannerTest.ps1:326 char:17
+ $users = $users.Add($item)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : ConvertToFinalInvalidCastException
Also, since i am already asking about powershell items.. Does anyone know what how to get LogonTime, KickoffTime via powershell? Adn what is ACB?
Santiago Squarzon's helpful answer uses a good approach with regex matching. We can further enhance the regex matching to include a combination of wildcard and exact matches.
$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
# List users who don't match $ignore
$users -notmatch $regex # Jane Doe, John Doe, Administrator not listed
If you must use an ArrayList, you can apply the same technique as above by
removing successful matches:
[collections.ArrayList]$users = 'John Doe','Wolverine','Mr. Pants','Administrator','Jane Doe','Master Administrator'
# two exact matches and one wildcard match
$ignore = 'john doe','jane doe','admin*'
$regex = '^({0})$' -f (($ignore -replace '\*','.*') -join '|')
$users -match $regex | Foreach-Object {
[void]$users.Remove($_)
}
Explanation:
In regex, alternations (basic OR logic) are separated by |. .* greedily matches any number of characters in a string. ^ denotes the beginning of a string. $ denotes the end of a string. Putting that all together, your exact matches would look like ^exact$ and your partial matches would look like ^admin.*$ (for admin*) or ^.*admin.*$ (for *admin*). Using alternation, you can combine those matches for an OR effect with ^(admin.*|Benjamin Dover|Johnny Lawrence)$.
What you're trying to do could be simplified to something like this:
Create a test case, the array will have User00 to User10 plus Administrators and Domain Admins.
$ignore = #(
'Administrators'
'Domain Admins'
'User05'
'User10'
)
[System.Collections.ArrayList]$users = 0..10 |
ForEach-Object { [pscustomobject]#{Name="User{0:00}" -f $_} }
$users.Add([pscustomobject]#{Name="Administrators"})
$users.Add([pscustomobject]#{Name="Domain Admins"})
Now you can simply use the .Where(..) method filtering those elements of the $users array that do not match the $ignore array on one side and those that do match on the other side.
$toIgnore = $ignore -join '|'
$notIgnoredUsers, $ignoredUsers = $users.Where({$_.Name -notmatch $toIgnore},'Split')
$notIgnoredUsers
Name
----
User00
User01
User02
User03
User04
User06
User07
User08
User09
$ignoredUsers
Name
----
User05
User10
Administrators
Domain Admins
As for how you're using the .Remove(..) method, since it doesn't produce any output, by doing $users = $users.Remove($item) you're actually setting the variable to null:
PS /> [System.Collections.ArrayList]$arr = 'one','two','three'
PS /> $arr = $arr.Remove('one')
PS /> $null -eq $arr
True
If you want to remove one from the list you would simply do $arr.Remove('one').

Get computers last logon with powershell

I have found the following script, but not able to get the expected result: computer, lastlogon.
Using Windows 10.
For some reason I am getting this error:
Get-ADComputer : Variable: 'NTName' found in expression: $NTName is not defined.
At line:10 char:19
+ ... astLogon = (Get-ADComputer -Filter {Name -eq $NTName} -Properties Las ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Get-ADComputer], ArgumentException
+ FullyQualifiedErrorId : ActiveDirectoryCmdlet:System.ArgumentException,Microsoft.ActiveDirectory.Management.Commands.GetADComputer
And a random number of dates and times:
04/11/2017 07:16:44 04/26/2017 01:08:31 04/26/2017 22:33:51 05/01/2017 07:05:13 04/24/2017 09:01:12 04/25/2017 06:36:43 08/21/2020 21:27:23 01/11/2018 11:47:14 12/27/20
20 14:37:44 01/02/2021 11:24:41 01/01/2021 13:48:16 12/31/2020 14:12:24 01/01/2021 06:57:39 01/05/2021 14:14:14 01/05/2021 21:04:12 01/06/2021 04:37:42 10/13/2015 22:53:5
2 01/01/2015 17:58:55 11/04/2015 04:02:00 06/02/2015 18:46:48 03/23/2011 05:21:05 01/11/2013 23:59:16 01/10/2012 09:46:40 10/16/2015 22:11:38 10/16/2015 22:05:24 04/24/20
18 12:12:35 07/26/2017 13:50:14 04/24/2012 14:22:04 11/17/2014 17:44:11 05/18/2016 13:52:34 03/15/2015 08:52:39 07/21/2016 08:46:35 11/19/2015 06:37:05 02/26/2015 11:50:5
0 02/23/2015 20:03:49 01/22/2015 10:49:49 12/08/2015 14:52:41 02/17/2017 11:05:13 09/08/2015 13:11:49 05/24/2015 13:17:40 05/12/2015 00:51:28 11/05/2015 13:44:19 10/28/20
15 23:28:23 07/23/2015 18:28:34 11/17/2015 17:29:39 10/24/2018 23:43:50 02/15/2016 10:15:05 04/07/2015 10:08:14 02/07/2019 06:07:01 07/26/2016 16:19:09 08/25/2015 12:08:1
9 10/25/2018 05:26:48 03/07/2015 03:30:19 06/10/2015 12:00:27 02/20/2015 10:15:37 08/04/2015 08:48:43 04/14/2015 05:58:17 08/26/2015 16:10:23 02/20/2017 21:02:25 03/14/20
18 14:15:23 08/25/2015 16:45:42 07/09/2015 05:12:25 03/02/2015 13:18:07 04/14/2015 05:37:20 04/22/2015 03:42:14 09/14/2015 13:51:48
# Specify CSV file of computer names.
$File = ".\Computers.csv"
$Computers = Import-Csv -Path $File
ForEach ($Computer In $Computers)
{
$NTName = $Computer.Name
# Retrieve last logon date of the computer (accurate with 14 days).
$LastLogon = (Get-ADComputer -Filter {Name -eq $NTName} -Properties LastLogonDate).LastLogonDate
"$NTName, $LastLogon"
Give this a shot and see if it works.
Please note: ensure that there is a column in the CSV with the first block actually being "Name". That's where $Computers1.Name references for the computer names in that column.
#I would recommend putting the whole file path to your csv here and not just '.\'
#This is the location of your CSV
$File = ".\Computers.csv"
#Here you're importing the CSV into your console and storing it into the $Computers1 variable.
$Computers1 = Import-Csv -Path $File
#Here youre using the $Computers variable to store the computer names being referenced in the "Name" column in your CSV, hence the $Computers1.Name.
#$Computers1.Name like mentioned above, this is just referencing the column beginning with "Name" (assuming this is where the computer names are)
$Computers = $Computers1.Name
#Here youre assigning the variable $Computer to each Computer name stored in $Computers and telling it to do the following FOR EACH Computer in Computers.
ForEach ($Computer In $Computers)
{
#Here you're just querying AD against the list of computers with it referencing $Computer as each computer(like mentioned above), and storying the result in $LastLogon.
$LastLogon = Get-ADComputer -Identity:$Computer -Property LastLogonDate | Select-Object -ExpandProperty LastLogonDate
# Retrieve last logon date of the computer (accurate with 14 days).
#This is just outputting to your console screen your computer name($Computer) and, the result for your Last Logon query ($LastLogon).
"$Computer - $LastLogon"
}

Powershell commandline with pipes error using Exchange 2010

Being new to powershell and used to oneliners in unix I find powershell strange. The below code gives me a "too many pipes" type of error. Can anyone tell me what I am doing wrong. My last step will be to add code that adds permission if not found in the else block.
[PS] C:\Windows\system32>(Get-mailbox -identity moh | select alias, distinguishedname) | foreach-object -process { if($_.distinguishedname -match "HK1|VO[1-9]") { $alias = $_.alias; get-mailboxfolderstatistics -identity $alias | Where {$_.FolderType -eq 'Calendar'} | %{ $calpath = $_.folderpath.substring(1); Get-MailboxFolderPermission -Identity $alias":\"$calpath -User iTell | %{ if($_.AccessRights -eq 'Editor') { write-host "Editor!" } else { write-host $_.AccessRights }} } } }
I get the following error.
Pipeline not executed because a pipeline is already executing. Pipelines cannot be executed concurrently.
+ CategoryInfo : OperationStopped: (Microsoft.Power...tHelperRunspace:ExecutionCmdletHelperRunspace) [], PSInvalidOperationException
+ FullyQualifiedErrorId : RemotePipelineExecutionFailed
Got it. Had to encapsulate blocks of code with parenthesis. But I thought the pipe block was just some sort of a write lock. In here I was only getting data and hence ought to be able to read from the streams.