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
Pile Of Garbage
May 28, 2007



mystes posted:

I have to say that confusing coercion of return types is one of the most annoying things about powershell.

The Invoke-RestMethod cmdlet is definitely an odd one. It tries to parse the HTTP response body and then returns a different object depending on the format of the data. From the cmdlet description:

quote:

PowerShell formats the response based to the data type. For an RSS or ATOM feed, PowerShell returns the Item or Entry XML nodes. For JavaScript Object Notation (JSON) or XML, PowerShell converts, or deserializes, the content into objects.

This inconsistent behaviour has led Microsoft to make some questionable decisions when extending the cmdlet, specifically when they added the -ResponseHeadersVariable parameter in PowerShell 6. Because there's no standard return type they couldn't easily extend it so they did this which is super gross IMO:

quote:

Creates a Response Headers Dictionary and saves it in the value of the specified variable. The keys of the dictionary will contain the field names of the Response Header returned by the web server and the values will be the respective field values.

Honestly this behaviour is problematic and if you need consistency it would probably be better to use Invoke-WebRequest which always returns the raw HTTP response in a BasicHtmlWebResponseObject. The response body is just a string so you can then parse and deserialise as required.

Adbot
ADBOT LOVES YOU

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

Pile Of Garbage posted:

The response body is just a string so you can then parse and deserialise as required.

Can't wait to parse some HTML with regex.

Pile Of Garbage
May 28, 2007



Toshimo posted:

Can't wait to parse some HTML with regex.

Or you could just pass the string to ConvertFrom-Json like a sane person

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

Buddy...

Pile Of Garbage
May 28, 2007




We're talking about communicating with an API that returns JSON strings in HTTP response body, not scraping a HTML webpage as you seem to have assumed.

mystes
May 31, 2006

Toshimo posted:

Can't wait to parse some HTML with regex.
Invoke-WebRequest actually used to parse html by default, but they specifically changed it in core.

Iirc, on older versions unless you pass -UseBasicParsing it will fail if you've never run internet explorer as the user you've run powershell as because the parsing somehow uses internet explorer.

I think the assumption is that the response isn't going to be html most of the time now, contrary to when powershell was first released.

I don't think there is good built in html parsing in .net so you would need to use the package that wraps html agility kit if that's really what you want. I do wish that it was built into .net but that's a separate issue.

mystes fucked around with this message at 09:14 on Aug 8, 2020

skipdogg
Nov 29, 2004
Resident SRT-4 Expert

Is there a way for powershell to wait for a command to finish in a for each loop before moving on to the next one? Google has lead me to start-process possibly.

More info:

I'm migrating a bunch of file shares and I have to run an .exe based utility against each one. Currently I'm doing this manually and it sucks. If I put the .exe in a for each loop though I think it's going to launch the .exe X number of times, which on some file servers could be 200+ shares. To add a wrinkle I need to modify the argument list of the .exe for every iteration

this isn't exact code, but loosely an idea of what I'm thinking about

code:
$shares = get-content c:\sharename.txt

foreach ($share in $shares){

c:\migration\$share\utility.exe /arg1 /arg2 /arg3     

Now I'm pretty sure if I run that it'll spawn utility.exe for however many $shares there are. What I need is for it to just run one at a time and when one finishes it launches another. In a perfect world I'd like to be able to run 4-6 iterations of utility.exe at a time, but I won't get greedy.

If someone could point me in the right direction I'd appreciate it.

Collateral Damage
Jun 13, 2009

You could use Start-Process and then check the HasExited property of the returned object to throttle how many are spawned at a time.

$process_object = Start-Process -FilePath "c:\migration\${share}\utility.exe" -ArgumentList "/arg1","/arg2","/arg3" -PassThru

if ($process_object.HasExited) { spawn more processes }

Plus whatever code you need to limit that to spawning a handful of processes at a time.

e: Alternatively, use Get-Process and simply count how many instances of utility.exe you have running, assuming nothing else is running it at the same time. This is probably easier.

code:
$max_processes = 4
foreach ($share in $shares) {
  while ((Get-Process -Name "utility.exe").Count -ge $max_processes) {
    Start-Sleep -Seconds 1
  }
  Start-Process -FilePath "c:\migration\${share}\utility.exe" -ArgumentList "/arg1","/arg2","/arg3"
}

Collateral Damage fucked around with this message at 17:46 on Aug 24, 2020

Irritated Goat
Mar 12, 2005

This post is pathetic.
I'm trying to make my scripts more "sharable" in the sense that it isn't calling for a specific named server\ip address. If I was doing somthing like

code:

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri [url]http://exchange.domain.com/powershell[/url] -Authentication Kerberos -Credential $UserCredential 

I want to be able to share it regardless of if that Exchange server croaks, it still works with the new one.

After that, I get to figure out the best way to deal with timeouts on VPN for AD queries cause :sigh:

The Fool
Oct 16, 2003


Irritated Goat posted:

I'm trying to make my scripts more "sharable" in the sense that it isn't calling for a specific named server\ip address. If I was doing somthing like

code:

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri [url]http://exchange.domain.com/powershell[/url] -Authentication Kerberos -Credential $UserCredential 

I want to be able to share it regardless of if that Exchange server croaks, it still works with the new one.

The first solution I can thought of is to setup a cname for your script to point to, but then someone needs to maintain the cname record.

DNS load balancer might work too.

Another solution would be to maintain a list of servers in a csv or json file, then reference that in your scripts.

Or you could of course just make the person running your script specify the server at run time.


quote:

After that, I get to figure out the best way to deal with timeouts on VPN for AD queries cause :sigh:

you can control timeout values in ad, otherwise reduce the size of the dataset you're querying in one go. I think most of the get-ad cmdlets support pagesize

Irritated Goat
Mar 12, 2005

This post is pathetic.

The Fool posted:

Or you could of course just make the person running your script specify the server at run time.

I think I'm going to do it this way. It removes hardcoded junk and I'm decent at remembering server names\IP addresses.

The Fool posted:

you can control timeout values in ad, otherwise reduce the size of the dataset you're querying in one go. I think most of the get-ad cmdlets support pagesize

The one that's timing out the most for me is just getting name, locked out status, pwd expiration status, pwd last set. If I run it a 2nd time, it usually works but it's kind of annoying.

Toast Museum
Dec 3, 2005

30% Iron Chef

The Fool posted:

Another solution would be to maintain a list of servers in a csv or json file, then reference that in your scripts.

Or you could of course just make the person running your script specify the server at run time.

Hell, do both. Make -ConnectionUri a non-required script parameter, and include a list of servers (or have the script obtain a list of servers) for the script to try if that parameter isn't bound.

Potato Salad
Oct 23, 2014

nobody cares


Irritated Goat posted:

timeouts on VPN for AD queries

consider maintaining your infrastructure from a vm at the relevant site

mllaneza
Apr 28, 2007

Veteran, Bermuda Triangle Expeditionary Force, 1993-1952




Potato Salad posted:

consider maintaining your infrastructure from a vm at the relevant site

I've long maintained a stack of old machines in my cube that I use for testing scripts and as jump boxes for AD administration, or for letting scripts run overnight. A VM or an old PC on-site that you control can be a blessing when you're remote. Several times jump boxes have let the team fire off push scripts for a deployment at 5pm Friday and then go the gently caress home.

thebigcow
Jan 3, 2001

Bully!

Collateral Damage posted:

You could use Start-Process and then check the HasExited property of the returned object to throttle how many are spawned at a time.

$process_object = Start-Process -FilePath "c:\migration\${share}\utility.exe" -ArgumentList "/arg1","/arg2","/arg3" -PassThru

if ($process_object.HasExited) { spawn more processes }

Plus whatever code you need to limit that to spawning a handful of processes at a time.

e: Alternatively, use Get-Process and simply count how many instances of utility.exe you have running, assuming nothing else is running it at the same time. This is probably easier.

code:
$max_processes = 4
foreach ($share in $shares) {
  while ((Get-Process -Name "utility.exe").Count -ge $max_processes) {
    Start-Sleep -Seconds 1
  }
  Start-Process -FilePath "c:\migration\${share}\utility.exe" -ArgumentList "/arg1","/arg2","/arg3"
}

Try the -Wait switch for Start-Process if you just want one at a time.

Collateral Damage
Jun 13, 2009

thebigcow posted:

Try the -Wait switch for Start-Process if you just want one at a time.
skipdogg wanted to run multiple processes, but with limits on how many simultaneously.

sloshmonger
Mar 21, 2013
Then expect to learn a lot about runspaces. Which are great for that, if a little hard to get at.

Toast Museum
Dec 3, 2005

30% Iron Chef
If PowerShell 6/7 is on the table, it sounds like Start-ThreadJob might be worth looking into. I only discovered it a day or two ago and haven't played with it yet, but it sounds like it should work here.

mystes
May 31, 2006

Toast Museum posted:

If PowerShell 6/7 is on the table, it sounds like Start-ThreadJob might be worth looking into. I only discovered it a day or two ago and haven't played with it yet, but it sounds like it should work here.
That looks nice. This kind of thing was incredibly annoying in older versions of powershell.

Pile Of Garbage
May 28, 2007



mllaneza posted:

I've long maintained a stack of old machines in my cube that I use for testing scripts and as jump boxes for AD administration, or for letting scripts run overnight. A VM or an old PC on-site that you control can be a blessing when you're remote. Several times jump boxes have let the team fire off push scripts for a deployment at 5pm Friday and then go the gently caress home.

It is also Microsoft recommended best practice: https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/implementing-secure-administrative-hosts

Toshimo
Aug 23, 2012

He's outta line...

But he's right!
Trying to set something up for an installer and this is bugging the hell out of me.

CODE:
code:
$MSIResultCodes = (
    @{"0"="SUCCESS: Task Successful"},
    @{"1605"="SUCCESS: Task Successful - App not Installed"},
    @{"1618"="Failure: Another install in Progress"},
    @{"3010"="SUCCESS: Task Successful - Reboot Required"}
)

$ExitCode="1605"

$MSIResultCodes.$ExitCode
Works perfectly. Except if I turn strict mode on and get a PropertyNotFoundException for the last line.

Mario
Oct 29, 2006
It's-a-me!

Toshimo posted:

Trying to set something up for an installer and this is bugging the hell out of me.

CODE:
code:
$MSIResultCodes = (
    @{"0"="SUCCESS: Task Successful"},
    @{"1605"="SUCCESS: Task Successful - App not Installed"},
    @{"1618"="Failure: Another install in Progress"},
    @{"3010"="SUCCESS: Task Successful - Reboot Required"}
)

$ExitCode="1605"

$MSIResultCodes.$ExitCode
Works perfectly. Except if I turn strict mode on and get a PropertyNotFoundException for the last line.
You have $MSIResultCodes as an array of hashtables. Piping it to Get-Member claims it's a Hashtable because that's what the array is composed of, but $MSIResultCodes.GetType() doesn't try to be helpful like that and reveals the truth.

Try
code:
$MSIResultCodes = @{
    "0"="SUCCESS: Task Successful";
    "1605"="SUCCESS: Task Successful - App not Installed";
    "1618"="Failure: Another install in Progress";
    "3010"="SUCCESS: Task Successful - Reboot Required"
}

CarForumPoster
Jun 26, 2013

⚡POWER⚡
The the thread for powershell/how do I admin questions?

I have a Teams Team/O365 Group that has a channel for each project within the group. ~50 projects that run their ~3-6 month life cycle and retire to the "hidden" list.

I want my users to be able to send emails to the channel using outlook, but I dont want to copy paste every channel email address into the global address list.

The O365 group shows up automatically in the GAL see:
code:
Get-UnifiedGroup |ft DisplayName,HiddenFrom*
DisplayName		HiddenFromExchangeClientsEnabled		HiddenFromAddressListsEnabled
-----------------------------------------------------------------------
Org1                    	False                         					False
Org2                     	True                          					True
But what I want is for each channel in Org1, add the channel email address to the GAL.

Google didnt help, any suggestions?

EDIT: NVM, got it via MSGraph API

CarForumPoster fucked around with this message at 20:40 on Oct 22, 2020

TITTIEKISSER69
Mar 19, 2005

SAVE THE BEES
PLANT MORE TREES
CLEAN THE SEAS
KISS TITTIESS




(Names changed) We've had a couple of promotions/title changes and now "Barney" is no longer the Manager (in AD) for a bunch of people, instead "Jenny" is. Is it possible to use PS to change all of these people's Manager field to Jenny? They're all in the same OU.

The Fool
Oct 16, 2003


Yes.

Get-Aduser to get your list of users, iterate through, then set-aduser.
Iirc, the manager field needs to be a distinguished name or an ad object

Submarine Sandpaper
May 27, 2007


Yeah, set-aduser user -manager manager

TITTIEKISSER69
Mar 19, 2005

SAVE THE BEES
PLANT MORE TREES
CLEAN THE SEAS
KISS TITTIESS




Thank you both! For the initial question I took a second look and saw it was only ten users that needed updating, so I got those done by hand. However this will help with other OUs full of users that need updating.

Question: how do I filter the users by their job title? Some titles report to Manager A while some report to Manager B, etc.

Submarine Sandpaper
May 27, 2007


Read up on the get-aduser documentation, most properties can be filtered as a parameter rather then piping into a where. You can use -searchscope to specify OU. Pipe the result from this when it looks good to set-aduser -manager manager

Pile Of Garbage
May 28, 2007



IMO better to familiarise yourself with Get-ADObject as it's more flexible and faster than the object-specific equivalent cmdlets.

Dirt Road Junglist
Oct 8, 2010

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

Pile Of Garbage posted:

IMO better to familiarise yourself with Get-ADObject as it's more flexible and faster than the object-specific equivalent cmdlets.

I concur, especially on the flexibility.

LegoMan
Mar 17, 2002

ting ting ting

College Slice
I've been tasked with writing a script (that I already wrote in C but corporate security rules required us to shift to Powershell, don't ask) that removes the tedium of renaming a large number of files after a work activity.

I have code set up to pull the latest file from an SD Card (old rear end equipment) and rename it to a certain name. Now I want to take that file and copy/rename it 21 times (or however many pieces of equipment a certain factory has which I've set up as a variable at the top of the script so every facility can have it tailored to them)

$arm_path = "c:\agv\arm"
$disk_path = "c:\agv\disk"
$vis_path = "c:\agv\vis"
$agv_num = 21

These are values I'm using that can be changed.

I want to take the file name say "ARM01.dat" and rename it 02, 03, 04, etc until it reaches the agvnum value. I know I should use a for loop but having only been using Powershell for three days I don't know enough syntax to chop off the end two digits and replace them with the value.
Thanks

Toshimo
Aug 23, 2012

He's outta line...

But he's right!

LegoMan posted:

I've been tasked with writing a script (that I already wrote in C but corporate security rules required us to shift to Powershell, don't ask) that removes the tedium of renaming a large number of files after a work activity.

I have code set up to pull the latest file from an SD Card (old rear end equipment) and rename it to a certain name. Now I want to take that file and copy/rename it 21 times (or however many pieces of equipment a certain factory has which I've set up as a variable at the top of the script so every facility can have it tailored to them)

$arm_path = "c:\agv\arm"
$disk_path = "c:\agv\disk"
$vis_path = "c:\agv\vis"
$agv_num = 21

These are values I'm using that can be changed.

I want to take the file name say "ARM01.dat" and rename it 02, 03, 04, etc until it reaches the agvnum value. I know I should use a for loop but having only been using Powershell for three days I don't know enough syntax to chop off the end two digits and replace them with the value.
Thanks

Lot of questions here:
  1. Do you care what the original filename is?
  2. Do you need the "latest" file by time or "latest" file by number?
  3. Does the original filename dictate the ending files' names?

An example here does what you are asking, but doesn't make an effort to do that style of string replacement, it just decides the ending format is going to be "ARM##.DAT" and makes files accordingly. If you need something to do actual string replacement (because your filenames may change and you'll need to change the output), let me know and I'll mock that up, too.

code:
Set-StrictMode -Version Latest

Start-Transcript -Path "C:\agv\logs\AGV-Copy-$(get-date -UFormat %Y%m%d%H%M%S).log" -NoClobber

$arm_path = "C:\agv\arm"		#Assuming this is your source directory
$disk_path = "c:\agv\disk"		#Assuming this is your destination directory
$vis_path = "c:\agv\vis"		#IDK what this is.
$agv_num = 21

if( -not (Test-Path $arm_path)) {
    Write-Host "Source Directory not found at $arm_path"
    break
}

if( -not (Test-Path $disk_path)) {
    Write-Host "Disk Directory not found at $disk_path"
    break
}

if( -not (Test-Path $vis_path)) {
    Write-Host "Vis Directory not found at $vis_path"
    break
}

$latest_source = Get-ChildItem -Path $arm_path -Filter "ARM*.dat" | Sort -Descending LastWriteTime | Select -First 1

if( -not ($latest_source)) {
    Write-Host "No Valid Source File Found."
    break
}

Write-Host "Duplicating $($latest_source.Name) $agv_num times..."

foreach( $iteration in 1..$agv_num) {
    $pad_length = ([string]$agv_num).Length
    Copy-Item "$($arm_path)\$($latest_source)" -Destination "$($disk_path)\ARM$(([string]$iteration).PadLeft($pad_length,"0")).dat" -Force
    Write-Host "$($disk_path)\ARM$(([string]$iteration).PadLeft($pad_length,"0")).dat created."
}

Stop-Transcript

LegoMan
Mar 17, 2002

ting ting ting

College Slice
The sequence is such

1. User puts disk in drive
2. User hits button to copy file to local machine
3. (Part of step 2 but this is where I'm stuck) Machine renames ARM01 to ARM02, ARM03, ARM04... ARMXX where XX is the value I set at the top (I don't like doing global variables but I want other sites to be able to easily edit it for their site and it's small enough it doesn't really matter).

4. User hits button to copy all files in that directory to the disk (I'm pretty sure I can figure this out)

I want to put the renaming in each function (There's two, arm data and visual data). The button when pressed determines what file is pulled from disk. I already figured this part out. But as part of the arm data copy function I want it to also copy the file (which I already have set to be renamed ARM01) to the variations of the same name.


code:
Function armcopy () {get-childitem -path $disk_path -Filter "*ARM*.dat" | 
    where-object { -not $_.PSIsContainer } | 
    sort-object -Property $_.CreationTime | 
    select-object -last 1 | 
    copy-item -Destination (join-path $arm_path "M3AGVARM01.dat")}
Here's my code so far in the function.

LegoMan fucked around with this message at 02:52 on Oct 30, 2020

Toshimo
Aug 23, 2012

He's outta line...

But he's right!
Ok, cool. This should have everything, then.
code:
Set-StrictMode -Version Latest

Start-Transcript -Path "C:\agv\logs\AGV-Copy-$(get-date -UFormat %Y%m%d%H%M%S).log" -NoClobber

$arm_path = "C:\agv\arm"
$disk_path = "c:\agv\disk"
$vis_path = "c:\agv\vis"
$agv_num = 21

$source_prefix = "ARM"
$output_prefix = $arm_path + "\M3AGVARM"
$output_suffix = ".dat"

if( -not (Test-Path $arm_path)) {
    Write-Host "Arm Directory not found at $arm_path"
    break
}

if( -not (Test-Path $disk_path)) {
    Write-Host "Source Directory not found at $disk_path"
    break
}

if( -not (Test-Path $vis_path)) {
    Write-Host "Vis Directory not found at $vis_path"
    break
}

$latest_source = Get-ChildItem -Path $disk_path -Filter "*$($source_prefix)*.dat" | where-object { -not $_.PSIsContainer } | Sort -Descending LastWriteTime | Select -First 1

if( -not ($latest_source)) {
    Write-Host "No Valid Source File Found."
    break
}

Write-Host "Duplicating $($latest_source.Name) $agv_num times..."

foreach( $iteration in 1..$agv_num) {
    $pad_length = ([string]$agv_num).Length
    $source_path = $disk_path + "\" + $latest_source
    $output_path = $output_prefix + ([string]$iteration).PadLeft($pad_length,"0") + $output_suffix
    Copy-Item $source_path -Destination $output_path -Force
    Write-Host "$($output_path) created."
}

Stop-Transcript

LegoMan
Mar 17, 2002

ting ting ting

College Slice

Toshimo posted:

Ok, cool. This should have everything, then.
code:
I'm heading home from work but I'll try this out tomorrow. Thanks so much for your time.

LegoMan
Mar 17, 2002

ting ting ting

College Slice
It took some tweaking but your code was like the missing piece of the puzzle that let me finish. Thanks again. Sending you an SA gift card!

Pile Of Garbage
May 28, 2007



If you're using Test-Path to check for the existence of a file or directory then you'll want to include the -PathType parameter with value Leaf or Container respectively. Otherwise it doesn't care and returns true whether a file or directory is found.

Submarine Sandpaper
May 27, 2007


code:
(Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace root\cimv2\terminalservices) | gm | ? {$_.name -eq "SetAllowTSConnections"}
TypeName: System.Management.ManagementObject#root\cimv2\terminalservices\Win32_TerminalServiceSetting

Name                  MemberType Definition                                                                                                                           
----                  ---------- ----------                                                                                                                           
SetAllowTSConnections Method     System.Management.ManagementBaseObject SetAllowTSConnections(System.UInt32 AllowTSConnections, System.UInt32 ModifyFirewallException)

(Get-WmiObject -Class "Win32_TerminalServiceSetting" -Namespace root\cimv2\terminalservices).SetAllowTSConnections(1,1)
Exception calling "SetAllowTSConnections" : ""
IDK wtf. Maybe it's worthless now? I know I should be using cim but I was stealing from a script I made back in the win7 days, but it still exists and has a method. I've manually set 1,1 to uint32 as well with no luck.


I want it to work because of:
code:
(Get-CimClass -Namespace root\cimv2\terminalservices -ClassName Win32_TerminalServiceSetting).CimClassMethods
SetAllowTSConnections                      UInt32 {AllowTSConnections, ModifyFirewallException}    {Implemented}
But it seems to be a read only property (at least on w10 desktops). How the gently caress would I know that unless I try to set it with CIM for a more robust error?

Pile Of Garbage
May 28, 2007



You need to make sure that your PowerShell session is running as Administrator. I replicated your issue here on a Win10 (2004) machine, running unelevated PowerShell produces the garbage error whilst elevated PowerShell provides the expected result.

Adbot
ADBOT LOVES YOU

Submarine Sandpaper
May 27, 2007


ah that explains the error on my personal and the read-only exception.

But it's still an error on the client's machines:
Exception calling "SetAllowTSConnections" : "Invalid Operation"

Hopefully it just won't be needed for this client's task :shrug:
I think these folks have an AV that's been sold to me as blocking all this and I don't have the keys to prevent that.

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