Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Inspector_666
Oct 7, 2003

benny with the good hair
Alright, here's a rabbit hole I threw myself down today for no good reason:

Every now and then somebody wants to know who all is on the receiving end of a group address in out Google Apps environment. This is pretty simple, but we have some groups that contain groups, and this can go down 2-3 levels. (Ex: the devs@company.com group sends to a few individuals, and also to the product@company.com group, which in turn contains the product.interns@company.com group, which is only users)

I wrote a script using gShell that went down a level to grab all of the users, but it doesn't really present the information properly (doesn't show what groups people are in, just that they'd get a message sent to the top-level address) and only goes down one level.

To resolve the latter issue, I whipped up a recursive function where I feed the highest level group name into:

code:
function Get-Groups ($AcctName) {
    try {
        $GroupCheck = ((Get-GAGroupMember -GroupName $AcctName).toarray().email)
	$script:ActualGroups += $AcctName
        foreach ($subgroup in $GroupCheck){
            Get-Groups($subgroup)
        }
    }
    catch {
	$script:UserAccts += $AcctName
    }
}
(Unfortunately gShell doesn't evaluate the Get-GAGroupMember command as true/false like you can do with AD stuff, so it has to be try/catch rather than if. The $script: variables are just diagnostic to confirm it was sorting them correctly.)

This does the job as far as spitting out a list of the actual groups and then the users in all of them. But it takes a while to do it, so I was trying to make it actually create the group membership lists at it ran so I didn't have to take the output of this and then feed it through another function to get my output that I can send to whoever asked. I figured the "easiest" thing would be to make a hash table, where the key is the group name and the value is an array with the user accounts. But I have no idea how to do that, if it's even possible.

I tried to get it going, but the problem is that since $AcctName is getting fed everything, even the users, it just creates a key for each user, with the value of themselves, and then a key for each group that's empty. Is it even possible to do what I'm suggesting here?

EDIT: whatis is the only thing I miss from GAM but boy do I miss it sometimes.

Inspector_666 fucked around with this message at 23:41 on Jan 26, 2017

Adbot
ADBOT LOVES YOU

Inspector_666
Oct 7, 2003

benny with the good hair

anthonypants posted:

The gShell wiki helpfully leaves out what the commands' outputs look like, so what does Get-GAGroupMember return, and why does it have a ToArray() method?

The output is:

code:
Email : [email]my.namel@company.com[/email]
ETag  : "An MD5 hash generated per user"
Id    : 21 character long series of digits
Kind  : admin#directory#member
Role  : MEMBER
Type  : USER
And it's a
code:
TypeName: System.Collections.Generic.List`1[[Google.Apis.admin.Directory.directory_v1.Data.Member, gShell,Version=0.9.7.0, Culture=neutral, PublicKeyToken=null]]
Unfortunately, gShell takes pretty much everything as a System.String since it's pretty much just parsing it directly into an API call. .toArray().email gives me an array of just the member e-mail addresses.


Practically all of gShell's documentation is procedurally generated, it sucks.

Inspector_666
Oct 7, 2003

benny with the good hair

anthonypants posted:

Are Role or Type useful?

:suicide:

Jesus loving Christ I can't believe I didn't think of the TYPE field. Groups are Type: GROUP, to the shock of nobody.

Well hey I got to play with a recursive function!

(I'm going to blame this on my original script just making GBS threads out the membership of the top-level group into a csv and never thinking about why I was actually doing that conversion all the way out.)

Inspector_666
Oct 7, 2003

benny with the good hair

Avenging_Mikon posted:

How would I make this recursive in a range, or say all of the ones I'm interested in start with XYZ, and do all those without running the command 30 times?

Is the range a list of given groups? If so, make an array containing them ($grouplist = "group1", "group2", "group3") and then put that one-liner in a foreach.

code:
$grouplist = "group1", "group2", "group3"

foreach ($group in $grouplist) {
     Get-ADGroupMember -Identity $group | Get-ADUser -Properties Mail | Where-Object {$_.Mail -ne $null} | 
     Select-Object Name,Mail | Export-Csv -NoTypeInformation C:\path\to\emails.csv
}
(linebreak for the sake of tables, but it wouldn't break anything anyway)

If you want all of the groups to dump into one CSV, add "-append" to the end of the Export-Csv command, if you want different CSVs for each group, make it "C:\path\to\$group.csv

You can also automagically pull the groups by starting with a
code:
Get-ADGroup -filter {name -like "XYZ*"} |
and skipping the foreach. (I'm realizing that practically all of my scripts make use of at least one foreach :v:)

Inspector_666 fucked around with this message at 23:52 on Feb 8, 2017

Inspector_666
Oct 7, 2003

benny with the good hair

Zaepho posted:

Do you want the Group's email address or the users in the groups' email addresses?

If the latter everyone has you. If the former...

code:
get-ADGroup | where-object {$_.Name -like "XYZ*"} | select-object Name,Mail | Export-Csv -NoTypeInformation C:\path\to\emails.csv

If your environment has a lot of groups you're gonna want to filter in the Get-ADGroup cmdlet, not in a separate where-object. Even if it doesn't, you should still do it. Filter left!

Inspector_666
Oct 7, 2003

benny with the good hair

Zaepho posted:

You're right. I'm terrible about filtering in the AD commandlets. I can NEVER get it right without a few tries.

I'm the same, I always try to cram a -match or -include in there before I remember -like is about as fancy as I can get.

Inspector_666
Oct 7, 2003

benny with the good hair

Briantist posted:

For those of you in NYC: I'm presenting at the NYC PowerShell meetup at Microsoft in Times Square on Monday Feb 13.

If you're in the area come get some free pizza and heckle me.

Ugh, Times Square. But hey, free pizza and the topics sound pretty interesting.

Inspector_666
Oct 7, 2003

benny with the good hair

thebigcow posted:

You can make functions that themselves behave like cmdlets. https://technet.microsoft.com/en-us/library/hh360993.aspx

When you do stuff like this, is the best way to get it into your workflow to add a Set-Alias line to your profile.ps1 file? That's what I've been doing with my scripts but it seems hack-y.

Inspector_666
Oct 7, 2003

benny with the good hair

The Fool posted:

Make your own modules.

Welp, guess it's time to learn a thing.

Inspector_666
Oct 7, 2003

benny with the good hair

Walked posted:

Well worth learning.

Sapien PowerShell Studio actually has pretty good boilerplate code / module projects for learning what those look like. I think its a 30 or 60 day trial, too. Makes getting started with Modules really easy

Apparently all I had to do was add two lines to the existing script, rename it something that fits with the proper verb-noun style, save it as a .psm1 in the right place, and then import it the usual way.

I know there's the whole manifest file and I need to actually write proper help documentation for stuff, but that was a lot easier than I was expecting.

Inspector_666
Oct 7, 2003

benny with the good hair

Briantist posted:

Yeah modules are the way to go. Ensure they are well-formed modules with proper paths and manifests and then put them in a path that's included in variable cheese-cube mentioned, that way you can import them by name only.

I started separating out every function into its own .ps1 file (that matches the function name) for my modules, in folders that determine whether they get exported or not (like Public and Pivate). Then my .psm1 becomes boilerplate code like this:

code:
#Requires -Version 4.0

$Subs = @(
    @{
        Path = 'Private'
        Export = $false
        Recurse = $false
    } ,

    @{
        Path = 'Public'
        Export = $true
        Recurse = $true
    }
) 

$Subs | ForEach-Object -Process {
    $sub = $_
    $PSScriptRoot | Join-Path -ChildPath $sub.Path | 
    Get-ChildItem -Filter *-*.ps1 -Recurse:$sub.Recurse -ErrorAction Ignore | ForEach-Object -Process {
        try {
            . $_.FullName
            if ($sub.Export -or $Global:__MyModuleName_Export_All) {
                Export-ModuleMember -Function $_.BaseName
            }
        } catch {
            Write-Error -Message "Could not import $($_.FullName)"
        }
    }
}

Oh dag, that's cool as hell. I'm definitely going to look into breaking the existing scripts down into future modularity like this.

I'll probably also have to dive into the manifest files for my new hire script since it involves a lot of csv lookups and exporting stuff, and there's currently a lot of $PSScriptRoot scattered around a couple of other ones.

Inspector_666 fucked around with this message at 19:39 on Feb 16, 2017

Inspector_666
Oct 7, 2003

benny with the good hair

Briantist posted:

Do you do a whole try {} catch {} around it?

I either use #Requires -Module or Import-Module Module -ErrorAction Stop and leave it at that.

Doesn't #Requires always stop the script if it fails? Seems like anything else would kind of defeat the purpose.

Inspector_666
Oct 7, 2003

benny with the good hair
There's Office and StreetAddress, but yeah, no PhysicalDeliveryOfficeName.

Just do a Get-Aduser your.name -properties * to double check.

Inspector_666
Oct 7, 2003

benny with the good hair
I'll actually be able to make it back tonight! Hooray!

Wait crap no I can't :argh:

I swear I'll get to another meetup one of these days!

Inspector_666 fucked around with this message at 21:06 on Jul 10, 2017

Inspector_666
Oct 7, 2003

benny with the good hair
I used to have a script that was a wrapped around GAM, and I had to write the commands like so:

code:
cmd /c .\gam.exe update group $grpNameAdd add member $usrName 2>&1 | %{ "$_" }
Try adding the piped bit at the end, which will turn the output into a string* and should stop Powershell from freaking out.

Note that error handling in this case requires you to scrape the output for the codes though. (I've since re-written it using gShell which makes my life much easier even if there are some API hooks missing from that module.)


*Is that right? I'm pretty sure I just lifted that workaround wholesale from Stack Overflow.

Inspector_666 fucked around with this message at 22:00 on Jul 24, 2017

Inspector_666
Oct 7, 2003

benny with the good hair
In my user termination script, I have a variable $findUser that's just the result of

code:
Get-ADUser -filter { samaccountname -eq $usrName }
(Obviously adjust the filter depending on how you're doing it.)

Then to move them into the Disabled User Accounts OU is:

code:
Get-ADObject -Identity $findUser | Move-ADObject -TargetPath "OU=Disabled User Accounts,DC=corp,DC=contoso,DC=com"
...and it's worked without issue.

I think that you might be over-defining the identity in the example line you posted, but piping the object from Get-ADObject works.

EDIT: I use the $findUser variable elsewhere in the script (removing from groups, disabling, password reset, etc), it's a good kind of "catch-all" for the AD identity for everything I need to do.

Inspector_666 fucked around with this message at 20:34 on Aug 29, 2017

Inspector_666
Oct 7, 2003

benny with the good hair

nielsm posted:

Or splat it:
code:
$taskArgs = @{
    TaskName = "TestRail Background Task"
    Trigger = (New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-Timespan -Minutes 1) -RepetitionDuration ([System.TimeSpan]::MaxValue))
    Action = (New-ScheduledTaskAction -Execute "C:\Program Files (x86)\PHP\v5.6\php.exe" -Argument "E:\TestRail\task.php")
    Settings = (New-ScheduledTaskSettingsSet -AsJob -ExecutionTimeLimit 1h -MultipleInstances StopExisting)
}
Register-ScheduledTask @taskArgs

Do this, it's the preferred method to ` from everything I found.

Inspector_666
Oct 7, 2003

benny with the good hair

scuz posted:

Real simple one for y'all cuz I'm super new to PowerShell.

I need a script that I can have a scheduled task run that will check to see if a service is running (windows firewall in this case) and send an email if it isn't. I've tested the "send-email" portion which works just fine, I know how to check a service, but I have no idea how to put the two together.

I assume you're using Get-Service to check the status? If so, you'd just need an if block that checked (Get-Service MpsSvc).status to see if it was "Running" or something else.

code:
$FirewallStatus = (Get-Service MpsSvc).status

if ($FirewallStatus -ne "Running") {
     <SEND EMAIL CODE GOES HERE>
}
I would probably put that all in a try catch block too, just in case the service got real messed up and it caused Get-Service to throw an error.

code:
$FirewallStatus = (Get-Service MpsSvc).status

try {
    if ($FirewallStatus -ne "Running") {
        <SEND EMAIL CODE GOES HERE>
    }
}
catch {
    <SEND EMAIL GOES HERE TOO MAYBE WITH EXTRA INFO THAT THE SERVICE CAN'T BE FOUND>
}

Inspector_666 fucked around with this message at 17:22 on Jun 20, 2018

Inspector_666
Oct 7, 2003

benny with the good hair

PBS posted:

You're missing a closing " on line 4.

Aha, I see my clever check to make sure people aren't just blindly copy-pasting code into their terminals worked!


Whoops, fixed it.

Inspector_666
Oct 7, 2003

benny with the good hair
I might be easier to just

code:
Get-ADuser -Filter * -Properties msExchRequireAuthToSendTo
and export the data to a CSV, especially if you're not sure the value of one of the things you want. Then you can filter and sort it in Excel.

Inspector_666
Oct 7, 2003

benny with the good hair
You can try scoping the variable with $global:description or $script:description depending on what's defining it? (I'd start with $script: if it's possible.)

Inspector_666
Oct 7, 2003

benny with the good hair
I'm not 100% but I'm pretty sure you only ever want to use "Format-List" to make something human readable rather than when you're trying to pipe the value around.

Inspector_666
Oct 7, 2003

benny with the good hair

TITTIEKISSER69 posted:

I need to find out how many AD users in a certain OU have extensionAttribute7 populated, and export this to a .csv file. I'm sure it'll start with Get-ADuser, but as a newb to this I'm having trouble fleshing out the rest. Can anyone help?

code:
Get-ADUser -Filter * -Properties extensionAttribute7 | Select Name,SamAccountName,extensionAttribute7 | Export-CSV -Path <WHEREVER YOU WANT TO PUT THE CSV> -NoTypeInformation
should get you the CSV showing the user's name, AD login, and the value of extensionAttribute7. This'll pull everybody in AD, you can then filter in Excel to not see any of the empty ones. That's the simplest way to do it.


EDIT: to answer the question of how to search in a specific OU, use the -SearchBase flag in Get-ADUser with the distinguished name of the OU.

Inspector_666
Oct 7, 2003

benny with the good hair

The Fool posted:

Instead of
code:
Get-ADUser -Filter *
Use

code:
Get-AdUser -Filter {extensionAttribute7 -like "*"}
And you will have your results filtered for you.

I always forget the correct syntax for -Filter, thanks for this. I was trying to concoct a much more complicated loop to check the values and make an array, etc.

Inspector_666 fucked around with this message at 19:39 on Jan 22, 2019

Inspector_666
Oct 7, 2003

benny with the good hair

CzarChasm posted:

I'm on 4.0, thought I was on 5. I'll do the update to 5 after the weekend and see if that fixes it.

Try

code:
Find-Module powershellget | Install-Module
instead of just Install-Module. I get the same error as you if I try to just use Install, but the piped command works without issue. This goes for pretty much all of the modules I need to download.

Inspector_666 fucked around with this message at 18:25 on Dec 27, 2019

Inspector_666
Oct 7, 2003

benny with the good hair
I mean yeah, update PShell regardless, but even in 5.1 I have to use the piped command.

Inspector_666
Oct 7, 2003

benny with the good hair
Ok this is probably a dumb question that I am not googling correctly but: Is there a way I can get the Exchange Online cmdlets to populate in VSCode so I can tab-complete them/the parameters while working on scripts? For example Get-CalendarProcessing or Get-User and the like?

Inspector_666
Oct 7, 2003

benny with the good hair

Toast Museum posted:

I tried it just now. After running Install-Module ExchangeOnline from PowerShell, Intellisense includes the module's cmdlets in VSCode without any additional configuration. Do you have the PowerShell extension installed in VSCode?

Yep, that did it. I knew it was something dumb I wasn't thinking of. I seldom use the integrated PS terminal so I always forget about it.

Inspector_666
Oct 7, 2003

benny with the good hair
I'm back again with a dumb question I can't properly articulate into Google.

I'm trying to generate a list of all of our groups in O365 along with their owner(s). That's easy enough:

code:
PS C:\Users> Get-Group -Identity * -ResultSize Unlimited | select DisplayName,SamAccountName,WindowsEmailAddress,ManagedBy | Export-Csv -Path "C:\Users\Documents\PSReporting\AllGroupsManagedBy.csv"
But the issue is that if there are multiple group owners, the array gets converted into a string and just gets slapped into a single cell in the CSV. I realize I can't have it keep the comma between them when it Exports even with a different delimiter, but is there any way to get it to just put them in separate cells?


The output is Full Name Full Name, so I guess it might just be easier to convert the CSV into a proper Excel file and just do the string manipulation there.

Inspector_666
Oct 7, 2003

benny with the good hair

The Fool posted:

Maybe a calculated property?

E: https://4sysops.com/archives/add-a-calculated-property-with-select-object-in-powershell/

Phone posting, but maybe I’ll write up an example when I get back to my desk.

Yeah, I was poking around in here but I had trouble getting the actual values to spit out instead of the class, and making the whole collection into a variable is specifically warned against in the Powershell output since the Get-Group return is almost 4000 items. I think working with the values in Excel is going to be the best way forward.

Inspector_666
Oct 7, 2003

benny with the good hair
Ideally I would just want 2 cells for the ManagedBy value, one with each name (yes, that's the "Owner" I'm talking about.) Pretty much in my perfect world it would just feed the array into the CSV as is, the comma would split the value into 2 cells and I could just add another column header in the final output.

Inspector_666
Oct 7, 2003

benny with the good hair

FISHMANPET posted:

Hmm, that doesn't seem very useful for doing anything but handing to humans to read (and I don't think calculated properties can do a dynamic number of properties anyway) so Excel may be your best bet.

If you knew what the max number of owners you would have, you could do something like @{label="Owner 1";expression={$_.ManagedBy[0]}} and so on for the max number...

Yeah I may just keep playing with the data to get that max number, I doubt it's very high. The idea here is initially just getting it looked at by humans, but I figured if it was multiple cells on one line I can just reverse whatever split there is to feed it back into the system in the future if need be.

Also that's good to know about the variable, Powershell pops up a warning over 1000 objects and I just didn't want to accidentally nuke the remote box I'm working on.

Inspector_666
Oct 7, 2003

benny with the good hair
Ok I checked and at least one group has 31 values in the ManagedBy field so yeah I think I may have gotten out in front of the issue. Thanks for your help though.

Inspector_666
Oct 7, 2003

benny with the good hair

TITANKISSER69 posted:

I'm thinking it would be more like the example linked here: https://community.spiceworks.com/topic/2232835-remove-member-from-delivery-management-with-exhange-powershell


The trick is I'm hoping I can literally enter companyname.office.* so all of the groups can be updated at once.

I'm not sure if you can wildcard that line, but you should be able to use Get-DistributionGroup -Filter or maybe -Anr to put the list into a variable you can then go through with a ForEach loop.

Inspector_666
Oct 7, 2003

benny with the good hair
It should also be pretty straightforward, use Get-ADUser with whatever filter you need to grab your disabled users to generate the list of mailboxes to check, then use Get-Mailbox to find which ones are forwarding to the oldmanager.

I'd probably put that output into a CSV to easily see what you're working with and then you can run the final script against it, which should just be a Set-Mailbox command.

Inspector_666
Oct 7, 2003

benny with the good hair
Send-Mailmessage is how you'd send the mail, too. Although apparently MS says you shouldn't use it, they also don't have a replacement so v0v


EDIT: Oh, I guess MailKit is the unofficial-but-still-MS-maintained solution now?

Inspector_666 fucked around with this message at 06:53 on Nov 27, 2021

Inspector_666
Oct 7, 2003

benny with the good hair
Had to dick around with the Slack API today and looking at the flags available for Invoke-RestMethod in 7.2 got me to install it and it's much nicer even though the API doesn't support the cool pagination stuff.

Inspector_666
Oct 7, 2003

benny with the good hair
I think the easiest thing would be to just use the Test-Path part in a simple if statement in the directory creation segment of the script rather than trying to integrate it into the switch.

Adbot
ADBOT LOVES YOU

Inspector_666
Oct 7, 2003

benny with the good hair

FISHMANPET posted:

Lol, ask any psgsuite questions you have. I'm, uh, one of the maintainers. In particular I've done a bunch around spreadsheets specifically.

God, this rules. I don't need Gsuite integration anymore since I'm in an O365 place now, but I remember back when I was learning how to use Powershell I was having to write my own wrappers for GAM that was scraping the text output for error codes and poo poo.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply