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
PierreTheMime
Dec 9, 2004

Hero of hormagaunts everywhere!
Buglord

LODGE NORTH posted:

I don't even know if this is the right place to ask this, but I'm dealing with a dilemma.

I have this script to run here:

code:
for file in *; do
if [[ -f "$file" ]]; then
mkdir "${file%.*}"
mv "$file" "${file%.*}"
fi
done
The script essentially takes the file names of the files inside the directory I run the script, makes a folder or folders with the same name as the file or files, and puts those items in their respective folder. If two items share the same name (different extensions) then it puts all of them into one folder with the same name. Easy stuff.

However, I need to get this to run on 100 separate folders. The way I do it now is Folder1/Folder2/Folder3/files where Folder 3 is where I cd to in order to make the split folders with the filenames etc etc.

The branches where things separate are beneath Folder 1. Folder 1 houses 100 folders (collectively referred to as Folder(s) 2), but in each of those folders is a folder (Folder 3) with the files within them that I need to run the command above on.

Here's a quick verbose attempt at what you're asking for:

PowerShell code:
$Folders = Get-ChildItem -Path '.\Folder1'
#Check all available folders in Folder1 (Folder2s)
ForEach ($Folder in $Folders) {
	$Subfolders =  Get-ChildItem -Path $Folder.FullName -Directory
	#Check all subfolders (Folder3s)
	ForEach ($Subfolder in $Subfolders) {
		$Files = Get-ChildItem -Path $Subfolder.FullName -File
		ForEach ($File in $Files) {
			#Evaluate if a folder exists already, make it if it doesn't, then move the file
			$FileFolder = "$($Subfolder.FullName)\$($File.Basename)"
			If (!(Test-Path -PathType Container -Path $FileFolder)) { New-Item -ItemType Directory -Force -Path $FileFolder }
			Move-Item $File.FullName -Destination $FileFolder
		}
	}
}
A lot of this can be reduced and it's mostly like this just for a better visualization of each step, here's the slightly-more-condensed version:
PowerShell code:
ForEach ($Folder in Get-ChildItem -Path '.\Folder1') {
	ForEach ($Subfolder in Get-ChildItem -Path $Folder.FullName -Directory) {
		ForEach ($File in Get-ChildItem -Path $Subfolder.FullName -File) {
			$FileFolder = "$($Subfolder.FullName)\$($File.Basename)"
			If (!(Test-Path -PathType Container -Path $FileFolder)) { New-Item -ItemType Directory -Force -Path $FileFolder }
			Move-Item $File.FullName -Destination $FileFolder
		}
	}
}

PierreTheMime fucked around with this message at 14:15 on Jul 18, 2019

Adbot
ADBOT LOVES YOU

sloshmonger
Mar 21, 2013

PierreTheMime posted:

Here's a quick verbose attempt at what you're asking for:

A lot of this can be reduced and it's mostly like this just for a better visualization of each step, here's the slightly-more-condensed version:
PowerShell code:
Mostly good code
This would depend on the OP's problem, but would miss files if Folder3 has subfolders that need to be checked they'd be missed.

PowerShell code:
ForEach ($Folder in Get-ChildItem -Path '.\Folder1') {
	ForEach ($Subfolder in Get-ChildItem -Path $Folder.FullName -Directory) {
		ForEach ($File in Get-ChildItem -Path $Subfolder.FullName -File -Recurse) {
			$FileFolder = "$($Subfolder.FullName)\$($File.Basename)"
			If (!(Test-Path -PathType Container -Path $FileFolder)) { New-Item -ItemType Directory -Force -Path $FileFolder }
			Move-Item $File.FullName -Destination $FileFolder
		}
	}
}

PierreTheMime
Dec 9, 2004

Hero of hormagaunts everywhere!
Buglord

sloshmonger posted:

This would depend on the OP's problem, but would miss files if Folder3 has subfolders that need to be checked they'd be missed.

While true, that wasn't in the requirement. :reject: I was basing the behavior based on the original shell, which only acted on files.

Good to point out though, if they did actually need that step.

VVV
That's a fair point as well.

PierreTheMime fucked around with this message at 18:46 on Jul 18, 2019

Pile Of Garbage
May 28, 2007



Yo stop using string concatenation/interpolation to construct file system paths! Best practice is to use Join-Path which can handle joining ambiguous path elements (Basically identical to os.path.join() in Python).

Dirt Road Junglist
Oct 8, 2010

We will be cruel
And through our cruelty
They will know who we are

Pile Of Garbage posted:

Yo stop using string concatenation/interpolation to construct file system paths! Best practice is to use Join-Path which can handle joining ambiguous path elements (Basically identical to os.path.join() in Python).

Holy crap, I have been missing out. :stare: That is so much more elegant.

Pile Of Garbage
May 28, 2007



Dirt Road Junglist posted:

Holy crap, I have been missing out. :stare: That is so much more elegant.

There's also Split-Path which can be used to extract specific parts of a file system path string:

code:
PS C:\> $Path = 'C:\Windows\notepad.exe'
PS C:\> Split-Path -Path $Path -Leaf
notepad.exe
PS C:\> Split-Path -Path $Path -Parent
C:\Windows
PS C:\> Split-Path -Path $Path -Qualifier
C:
And Test-Path which checks whether a file system path is accessible (Make sure to always include the PathType parameter to specify whether you expect it to be a file or folder). I like to use Test-Path with ValidateScript when I have a script/function parameter which expects a path to a file/folder.

Edit: all three *-Path cmdlets also work with UNC paths.

Pile Of Garbage fucked around with this message at 08:32 on Jul 20, 2019

my cat is norris
Mar 11, 2010

#onecallcat

Can I use Revoke-AzureADUserAllRefreshToken for ALL users in a given tenant, or do I have to do it by individual?

Is it as easy as Get-AzureADUser -all $true | Revoke-AzureADUserAllRefreshToken?

my cat is norris fucked around with this message at 20:03 on Jul 29, 2019

The Fool
Oct 16, 2003


my cat is norris posted:

Can I use Revoke-AzureADUserAllRefreshToken for ALL users in a given tenant, or do I have to do it by individual?

Is it as easy as Get-AzureADUser | Revoke-AzureADUserAllRefreshToken?

I've never done it, but the docs for Revoke-AzureADUserAllRefreshToken say it supports pipeline input, so it should work.

my cat is norris
Mar 11, 2010

#onecallcat

Thank you for confirming that for me! I'm always so afraid of these little scripts that have big impact.

Dirt Road Junglist
Oct 8, 2010

We will be cruel
And through our cruelty
They will know who we are

my cat is norris posted:

Thank you for confirming that for me! I'm always so afraid of these little scripts that have big impact.

What, you don't test your code in prod? :downs:

I mean, uh...I follow best practices and never do anything that might result in wiping out an entire OU's membership perms.

my cat is norris
Mar 11, 2010

#onecallcat

Man, okay. Second half of that question. I'm not having any luck forcing a password change on myself via PowerShell. I want to prompt users to change their passwords the next time they log into Office 365.

I'm trying this on my own account:

code:
Set-AzureADUser -ObjectID <my object ID> | Set-AzureADUserPassword -EnforceChangePasswordPolicy $true -ForceChangePasswordNextLogin $true
...but it doesn't seem to be doing jack poo poo. Any insight?

Wizard of the Deep
Sep 25, 2005

Another productive workday
If you're trying to pass by pipeline, I think you should be using "Get-AzureADUser".

nielsm
Jun 1, 2009



Wizard of the Deep posted:

If you're trying to pass by pipeline, I think you should be using "Get-AzureADUser".

Yeah, if it's at all similar to Set-ADUser then Set-AzureADUser does not return anything, unless you also give it the -PassThru flag. If you aren't actually setting anything you should just use Get-AzureADUser instead.

my cat is norris
Mar 11, 2010

#onecallcat

Omg thank you, that should have been so obvious!!

Whoooa errors:

code:
PS C:\Users\me> Get-AzureADUser -ObjectID <my object ID> | Set-AzureADUserPassword -ForceChangePasswordNextLogin $true

Set-AzureADUserPassword : The input object cannot be bound because it did not contain the information required to bind all mandatory parameters:  Password
At line:1 char:66
+ ... 02fcd968a | Set-AzureADUserPassword -ForceChangePasswordNextLogin $tr ...
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (class User {
  ...Type: Member
}
:PSObject) [Set-AzureADUserPassword], ParameterBindingException
    + FullyQualifiedErrorId : InputObjectMissingMandatory,Microsoft.Open.AzureAD16.Graph.PowerShell.Custom.Cmdlet.SetU
   serPassword

The Fool
Oct 16, 2003


While set-azurradpassword does support the pipeline, it's expecting an objectid string not a user object.

my cat is norris
Mar 11, 2010

#onecallcat

Okay, I've got that sorted out; thank you.

Is there a way to leave the -Password parameter blank so that I don't actually set anyone's passwords, or do I have to provide something? I really just want to force people to change their existing passwords.

The Fool
Oct 16, 2003


Not with set-azureradpassword, but set-msoluserpassword will.

my cat is norris
Mar 11, 2010

#onecallcat

Ahhh maybe I went down the wrong rabbit-hole, then. Thank you, I'll get more information about that and give it a shot.

Dirt Road Junglist
Oct 8, 2010

We will be cruel
And through our cruelty
They will know who we are
I have zero experience with Azure AD, but in regular AD, I've used something like this before to scramble PWs and check the "User must change password on next logon" box.

code:
$newpwd = Read-Host "Enter the new password" -AsSecureString

Set-ADAccountPassword -Identity $CHANGEwho -NewPassword $newpwd -Reset -PassThru | Set-ADuser -ChangePasswordAtLogon $True
I want to say I had a simple random hash for the new password (the -AsSecureString). Security didn't care what the scrambled pw was, just that it was scrambled. You could eliminate the Set-ADAccountPassword part if you don't want to scramble the PW.

cr0y
Mar 24, 2005



Is the book referenced in the OP still relevant? I am semi comfortable with powershell but its a lot of hacking and copying existing code and I want to become more well rounded. Also any reccomendations for getting learned up in PowerShell+PowerCLI?

Wizard of the Deep
Sep 25, 2005

Another productive workday
I couldn't find a direct reference to any book in the OP, but you should get Learn PowerShell in a Month of Lunches, 3rd Ed. poo poo's changed a lot since the OP was last updated in 2010, and a bunch of that stuff is even easier now.

Dirt Road Junglist
Oct 8, 2010

We will be cruel
And through our cruelty
They will know who we are

Wizard of the Deep posted:

I couldn't find a direct reference to any book in the OP, but you should get Learn PowerShell in a Month of Lunches, 3rd Ed. poo poo's changed a lot since the OP was last updated in 2010, and a bunch of that stuff is even easier now.

Yeah, v5 has added a lot of nice-to-haves.

Seconding the PS in a Month of Lunches as a good book to learn from.

cr0y
Mar 24, 2005



I keep hearing good things about that one, I'll grab it. Thanks!

cr0y
Mar 24, 2005



Wizard of the Deep posted:

I couldn't find a direct reference to any book in the OP, but you should get Learn PowerShell in a Month of Lunches, 3rd Ed. poo poo's changed a lot since the OP was last updated in 2010, and a bunch of that stuff is even easier now.

'Powershell in Action'
https://www.amazon.com/Windows-Powershell-Action-Bruce-Payette/dp/1932394907

Wizard of the Deep
Sep 25, 2005

Another productive workday

Ah, I see it now. If anything, get the 3rd edition, because the first edition linked in the OP is a dozen years old. I haven't read it, though. Don't take this as a recommendation.

Schadenboner
Aug 15, 2011

by Shine
Month of Lunches is a goddamn institution. Like the Camel Perl book or some poo poo.

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Pile Of Garbage posted:

Yo stop using string concatenation/interpolation to construct file system paths! Best practice is to use Join-Path which can handle joining ambiguous path elements (Basically identical to os.path.join() in Python).

I love Join-Path but its syntax can be cumbersome when you have to use it for may path components:

code:
$one = 'C:'
$two = 'Windows'
$three = 'System32'
$four = 'drivers'

$path = $one | 
    Join-Path -ChildPath $two | 
    Join-Path -ChildPath $three |
    Join-Path -ChildPath $four
So in some cases, it's nicer to use the .Net path method directly:

code:
$path = [System.IO.Path]::Combine($one,$two,$three,$four)
Note that Combine has overloads for 2, 3, or 4 paths, as well as one that takes an array, so it's great if your path components are already in an array or you have an arbitrary number of them.

In PowerShell Core, they added a parameter to help, but it's a still little meh to me because you still need a singular child:

code:
$one | Join-Path -ChildPath $two -AdditionalChildPath $three,$four
And besides just not having to do lovely concatenation yourself, a great reason to use these is that PowerShell is cross-platform, and this eliminates choosing which path separator to use, unless you're trying to build paths for a platform different than what the code is running on.

Djimi
Jan 23, 2004

I like digital data
So I'm stumped about the behavior Outlook.Application ComObject. Code snippet below looks in a folder in Outlook for emails received in the last 15 minutes.
It sometimes works (returns a count) and sometimes it doesn't (even though there is a message received in the time frame). That is $count is 0 even though it shouldn't be.
code:
$now = (get-date).ToString("MM/dd/yyyy HH:mm")
$previous = (get-date).AddMinutes(-15).ToString("MM/dd/yyyy HH:mm")

$ol = New-Object -ComObject Outlook.Application 
$ns = $ol.GetNamespace("MAPI") 
$PublicFolder  = $ns.Folders.Item("Public Folders - djimiATcontoso.com")
$public_root = $Publicfolder.folders.item("All Public Folders")
mydir = $public_root.folders.item("Alpha-Omega")

$sFilter="[ReceivedTime] > '{0:MM/dd/yyyy HH:mm}' AND [ReceivedTime] < '{1:MM/dd/yyyy HH:mm}'" -f $previous,$now

$items=(mydir.Items.Restrict($sFilter) | sort ReceivedTime -descending)
$count=$items.count
Write-Host "Count is " $count
$items| ForEach-Object { ... #do something based on the found emails
We're hosted in Office365 - is there a better method or object I can use, that can do the same thing but directly connect to MS, and leave Outlook.App out of it? Thank you :tipshat:

Zaepho
Oct 31, 2013

Djimi posted:

We're hosted in Office365 - is there a better method or object I can use, that can do the same thing but directly connect to MS, and leave Outlook.App out of it? Thank you :tipshat:
What you're looking for is EWS

Check the Microsoft.Exchange.WebServices.Data namespace. Unfortunately I don't have any good example code for you. you will need a O365 username/password pair you can get to to pass into the objects. I haven't had to try to get a 2FA protected account to work with this method.

PierreTheMime
Dec 9, 2004

Hero of hormagaunts everywhere!
Buglord

Zaepho posted:

What you're looking for is EWS

Check the Microsoft.Exchange.WebServices.Data namespace. Unfortunately I don't have any good example code for you. you will need a O365 username/password pair you can get to to pass into the objects. I haven't had to try to get a 2FA protected account to work with this method.

Note that non-OAuth EWS is going to be sunset soon: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Upcoming-changes-to-Exchange-Web-Services-EWS-API-for-Office-365/ba-p/608055

Well, within a year, but you might want to consider coding ~for the future~ and use Graph now to avoid headaches.

Zaepho
Oct 31, 2013

PierreTheMime posted:

Note that non-OAuth EWS is going to be sunset soon: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Upcoming-changes-to-Exchange-Web-Services-EWS-API-for-Office-365/ba-p/608055

Well, within a year, but you might want to consider coding ~for the future~ and use Graph now to avoid headaches.

Well Crap.. Luckily the customer's AD migration should be done by then. We're making EWS Calls to on On-Prem Exchange and O365 Exchange to copy Mail Contacts from user's mailboxes. The customer didn't want to do full email migration.

I guess I'm going to have to learn Graph then.

nielsm
Jun 1, 2009



This is not strictly a PowerShell question, but I suspect the regulars here tend to have experience with working with AD programmatically...

The UserAccountControl field on AD accounts has two flags that seem to never be set: LOCKOUT and PASSWORD_EXPIRED
Did they only get set by older versions of AD or even NTDS? If new versions don't set these flags, what's the best way to detect these states?

Toast Museum
Dec 3, 2005

30% Iron Chef

nielsm posted:

This is not strictly a PowerShell question, but I suspect the regulars here tend to have experience with working with AD programmatically...

The UserAccountControl field on AD accounts has two flags that seem to never be set: LOCKOUT and PASSWORD_EXPIRED
Did they only get set by older versions of AD or even NTDS? If new versions don't set these flags, what's the best way to detect these states?

I haven't played with that field myself, but it looks like a somewhat newer field is ms-DS-User-Account-Control-Computed, though that page doesn't indicate implementation beyond 2012, and I'm not sure what has superseded it.

In any case, Search-ADAccount should do what you need with the -LockedOut or -PasswordExpired parameters.

nielsm
Jun 1, 2009



Ah that computed field is probably what I want then. I'm not actually working in PowerShell for this, but rather using the System.DirectoryServices API from .NET in a C# desktop application, and later after making my previous post discovered that to use the msDS computed properties you have to explicitly request them. Requesting "all properties" in particular does not return them.

Djimi
Jan 23, 2004

I like digital data

Zaepho posted:

What you're looking for is EWS

Check the Microsoft.Exchange.WebServices.Data namespace. Unfortunately I don't have any good example code for you. you will need a O365 username/password pair you can get to to pass into the objects. I haven't had to try to get a 2FA protected account to work with this method.
Thank you.

PierreTheMime posted:

Note that non-OAuth EWS is going to be sunset soon: https://techcommunity.microsoft.com/t5/Exchange-Team-Blog/Upcoming-changes-to-Exchange-Web-Services-EWS-API-for-Office-365/ba-p/608055

Well, within a year, but you might want to consider coding ~for the future~ and use Graph now to avoid headaches.
Oh. OK. Hm.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams

Djimi posted:

code:
$items=(mydir.Items.Restrict($sFilter) | sort ReceivedTime -descending)
$count=$items.count
Write-Host "Count is " $count

The other answers are probably where you should go with this, but something that trips me up sometimes is that when there's only one object it won't be be in a container, and so it may not have a count property. If you're curious about making this code work, I'd see what happens when there's only one email returned by mydir.items.Restrict($sFilter) and see if it has a count property or not.

FISHMANPET fucked around with this message at 06:46 on Aug 16, 2019

nielsm
Jun 1, 2009



Pipe to Measure-Object to count zero, one, and many objects correctly.

FISHMANPET
Mar 3, 2007

Sweet 'N Sour
Can't
Melt
Steel Beams
You can also just force it into an array:
code:
$items=@(mydir.Items.Restrict($sFilter) | sort ReceivedTime -descending)
[code]
In this case you've got an extra set of parenthesis around everything, so if you just add the amersand it'll cast it into an array.
[code]
$items = @("one")
That's super contrived, especially because some of the builtin types properly do get a Count attribute even when there's a single item.
code:
"one".count
1.count
Those will both return a count of 1 like you'd expect.

nielsm
Jun 1, 2009



Toast Museum posted:

I haven't played with that field myself, but it looks like a somewhat newer field is ms-DS-User-Account-Control-Computed, though that page doesn't indicate implementation beyond 2012, and I'm not sure what has superseded it.

In any case, Search-ADAccount should do what you need with the -LockedOut or -PasswordExpired parameters.

Yeah definitely. These Computed properties are neat but you need to know they are available and only exist when you request them explicitly.

code:
Get-ADUser foo -Properties 'msDS-User-Account-Control-Computed','msDS-UserPasswordExpiryTimeComputed' |
  Select 'Name',
    @{N='Locked';E={($_.'msDS-User-Account-Control-Computed' -band 0x10) -ne 0}},
    @{N='PasswordExpires';E={[DateTime]::FromFileTime($_.'msDS-UserPasswordExpiryTimeComputed')}}
C# code:
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;

var dc = DomainController.FindOne(new DirectoryContext(DirectoryContextType.Domain));
var ds = dc.GetDirectorySearcher();
ds.Filter = "(objectClass=user)(sAMAccountName=foo)";
ds.PropertiesToLoad.AddRange(new string[] {
  "name",
  "msDS-User-Account-Control-Computed",
  "msDS-UserPasswordExpiryTimeComputed",
});
var user = ds.FindOne();
var userName = user.Properties["name"][0] as string;
var userLockedOut = ((long)user.Properties["msDS-User-Account-Control-Computed"][0] & 0x10) != 0;
var userPasswordExpires = DateTime.FromFileTime((long)user.Properties["msDS-UserPasswordExpiryTimeComputed"][0]);

nielsm fucked around with this message at 09:30 on Aug 16, 2019

Adbot
ADBOT LOVES YOU

Zaepho
Oct 31, 2013

FISHMANPET posted:

You can also just force it into an array:
code:
$items=@(mydir.Items.Restrict($sFilter) | sort ReceivedTime -descending)

I typically force results into an array by typing the variable.
code:
[array] $items=mydir.Items.Restrict($sFilter) | sort ReceivedTime -descending
This ensures I can always treat $items as an array no matter how many objects are returned.

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