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
Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

stubblyhead posted:

I need to do some remote admin on an AWS instance using powershell, but the machine I need to do it from can only access the web through a proxy server. Assuming the AWS server is configured correctly am I correct in thinking that all I need to do is specify the proxy details with New-PSSessionOption and feed that into New-PSSession?
I've never tried it before, but yeah that's how it should work. You could also use the PSSessionOption object in Invoke-Command or Enter-PSSession, and you can set the $PSSessionOption variable to set the default options going forward.

Adbot
ADBOT LOVES YOU

Wardende
Apr 27, 2013
Hi, newbie here... I'm trying to use the device management script/dlls from TechNet in a script, but every time I try to load them in, I get an operation not supported exception. Googling this tells me it's something to do with the files being blocked/prohibited, but I've unblocked them in the Properties window and still no go. Any ideas?

stubblyhead
Sep 13, 2007

That is treason, Johnny!

Fun Shoe
Are you using an elevated prompt?

Wardende
Apr 27, 2013

stubblyhead posted:

Are you using an elevated prompt?

You're right, that was it. Do I have to run an elevated prompt to import 3rd party scripts/libraries, or what's the pattern there? Thank you!

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Wardende posted:

You're right, that was it. Do I have to run an elevated prompt to import 3rd party scripts/libraries, or what's the pattern there? Thank you!
No, you don't need to be elevated to import modules or dot source scripts, but when you do that you are executing some code. If that code requires elevation, then you'll need to be elevated to do the import.

stubblyhead
Sep 13, 2007

That is treason, Johnny!

Fun Shoe

Briantist posted:

I've never tried it before, but yeah that's how it should work. You could also use the PSSessionOption object in Invoke-Command or Enter-PSSession, and you can set the $PSSessionOption variable to set the default options going forward.

I'm not sure if I'm doing the session settings wrong or if it's AWS fuckery getting in my way. If I'm reading it right winrm is only listening on its private IP addresses, and indeed the public IP isn't even listed for any of the adapters. I'm guessing Amazon NATs that out or something, but regardless the winrm service doesn't seem to be accessible from the internet at large.

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

stubblyhead posted:

I'm not sure if I'm doing the session settings wrong or if it's AWS fuckery getting in my way. If I'm reading it right winrm is only listening on its private IP addresses, and indeed the public IP isn't even listed for any of the adapters. I'm guessing Amazon NATs that out or something, but regardless the winrm service doesn't seem to be accessible from the internet at large.
Hm, I haven't used AWS yet, so that stuff does sound a bit weird but I'm not certain if it's the cause. You might also check the Windows firewall if it's enabled. It's likely to be blocking WinRM by default on the public profile at least.

stubblyhead
Sep 13, 2007

That is treason, Johnny!

Fun Shoe
It wasn't the Windows firewall, but you're on the right tack. When you start AWS instances you assign security groups that specify what kind of traffic you want to allow, and the group I used for my test server didn't have the right ports open. A couple quick changes and I can get in.

e: Actually I spoke too soon. I am able to connect directly to the instance, but going through a proxy appears to require https. A certificate is required to start an https listener, and I'm not sure a self-signed one will pass muster with my client (no CA in AWS we can use, and setting one up will probably get shot down as well). I think this is becoming more of an AWS question than a PS question, so I think I'll bow out at this point.

stubblyhead fucked around with this message at 23:00 on Jul 1, 2015

Hughmoris
Apr 21, 2007
Let's go to the abyss!
Powershell was giving me fits earlier today when trying to accomplish a simple task.

I have a file called patient.txt with contents similar to:
code:
Patient: Doe, John
Location: ICU
Account: 1234567890123
I was trying to write a very simple script that would read/regex the file, and output the patient location: ICU. What is the best way to approach simple regex captures in Powershell? I would post my code but unfortunately I left my laptop at work tonight.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Hughmoris posted:

Powershell was giving me fits earlier today when trying to accomplish a simple task.

I have a file called patient.txt with contents similar to:
code:
Patient: Doe, John
Location: ICU
Account: 1234567890123
I was trying to write a very simple script that would read/regex the file, and output the patient location: ICU. What is the best way to approach simple regex captures in Powershell? I would post my code but unfortunately I left my laptop at work tonight.

(get-content C:\temp\patient.txt) -match "^Location:+"

Output:
Location: ICU

Unless you want to find all of the patients where the location is ICU? Your question is poorly worded.

Hadlock
Nov 9, 2004

I got tired of assigning $global:variablename to a bunch of variables at the top of my script.

I just went through the process of building a class in Powershell to hold all my global variables. It's kind of excessive for short small scripts but if it's going to be over 200 lines long, that seems to be the break even point for doing it this way. Powershell's Intellisense (i.e. the popup windows that show up in Powershell ISE 4.0 and forward) really kicks in to high gear if you do it this way.

Once you have this setup, while you're typing, you can type $g.q and it will autocomplete as $g.queue if you hit the tab key. Just typing " $g. " will give you a list of all your global variables which is pretty nice. Heck, if you type $g = Gvars - it will autocomplete with i, type in a value, hit - and it will pick the next value, etc etc. It's very elegant and you don't have to go hunting for variable names at the top of the script, and perhaps better yet, you don't have to type "$global:" inside of all your functions.

Strongly borrowed from this blog post: https://powertoe.wordpress.com/2014/04/26/you-know-powershell-is-an-object-oriented-language-right/

Supposedly Powershell 5 (which is coming out with the release of Windows 10 at the end of this month) has more native support for Classes, but variable scope will always be a problem in Powershell so this should still be useful. And this way won't break in PS5, and this method works back to at least PS3, probably PS2

code:
#some static global variables
$arrivalrate = 2
$completionrate = 2
$iterations = 100

#region define a class & it's properties called 'GVars'
$GVars = new-object psobject -Property @{
    i = $null
    x = $null
    queue  = $null
    WorkCenter = $null
    counterW = $null
    completed = $null
}

#constructor
function GVars { #constructor
    param(
          [Parameter(Mandatory=$false)]
          [int]$i,
          [Parameter(Mandatory=$false)]
          [int]$x,
          [Parameter(Mandatory=$false)]
          [int]$queue,
          [Parameter(Mandatory=$false)]
          [int]$WorkCenter,
          [Parameter(Mandatory=$false)]
          [int]$counterW,
          [Parameter(Mandatory=$false)]
          [int]$completed
    )
    $gvar = $GVars.psobject.copy()
    $gvar.i = $i
    $gvar.x = $x
    $gvar.queue = $queue
    $gvar.WorkCenter = $WorkCenter
    $gvar.counterW = $counterW
    $gvar.completed = $completed
    $gvar
}

#start using class
$g = Gvars -i 0 -x 0 -queue 0 -WorkCenter 0 -counterW 0 -completed 0

$g
#endregion

function completedstats{
Write-Host "Task complete. Here are your stats"
Write-Host "completed" $g.completed
Write-Host "queue" $g.queue
}

function ifarrival{

if($g.x -ge $arrivalrate){
    #add user to queue
    $g.queue++

    #reset counter
    $g.x = 0

    }

}

function doloop{
do{
$g.i++
$g.x++

Write-Host $g.i " iteration___"

ifarrival

}until($g.i -ge $iterations)

}

doloop
completedstats



Hadlock fucked around with this message at 11:47 on Jul 3, 2015

12 rats tied together
Sep 7, 2006

Hadlock posted:

I got tired of assigning $global:variablename to a bunch of variables at the top of my script. [...]

I would go back to what I suggested originally which is for you to make a module. Simple as gently caress, barebones config would be:

code:
$env.psmodulepath\include_gvars.ps1:
    $global_variable_props = 
       @{
            'i' = $null
            'x' = $null
            'queue' = $null
            'workcenter' = $null
            'counterw' = $null
            'completed' = $null
        }
    
    $gvars = New-Object -TypeName psobject -Prop $global_variable_props (this could, and should probably be inside your script instead of the module, but its included here for reference)

$env.psmodulepath\include_gvars.psd1:
        GUID = '<some guid>'
        ScriptsToProcess = "/full/path/to/module.ps1"
        VariablesToExport = '*'
        etc

$your_script.ps1
        Import-Module include_gvars.psd1 # you now have $gvars, $gvars.i, $gvars.x, and so on (and yes, they autocomplete)
So, instead of defining global variables at the start of your script you can turn this into a function and have those be parameters. (saves 4 lines and makes the execution of this much more sane). You can also move the class definition to the module file (saves ~46 lines and lets you organize this and all other class definitions you might ever need to make). From that point you can probably just paste the remaining ~35 lines of your script and it should work without issue.

This is still also the "stupid" way of doing things, if you are going to need to create a lot of classes I would really recommend at taking a look at the .ps1xml files.

12 rats tied together fucked around with this message at 21:05 on Jul 3, 2015

UberJumper
May 20, 2007
woop
How can i convert an enum value into a string? For example i have the following code:

code:
Get-Service -name $my_filter | Select-Object -Property Name, Status
The thing is ansible's powershell helpers seem to convert status into a number value since it is an enum. The solution recommended is to convert Status into a string. Is there any easy way to convert this into a string? Without manually iterating through the entire array and changing each value?

*EDIT* I feel dumb:

code:
Get-Service -name $my_filter | Select-Object Name, @{Name="Status"; Expression={$_.Status.ToString()}} 

UberJumper fucked around with this message at 18:40 on Jul 5, 2015

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Hadlock posted:

I got tired of assigning $global:variablename to a bunch of variables at the top of my script.
Why do you have such a need for global variables? Typically if I find myself needing a global I take it as a sign that I've designed something poorly and refactor it. I apologize if you covered the reason in some previous post and I forgot it or missed it.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Briantist posted:

Why do you have such a need for global variables? Typically if I find myself needing a global I take it as a sign that I've designed something poorly and refactor it. I apologize if you covered the reason in some previous post and I forgot it or missed it.

Yeah, globals are one of the paths to mind-boggling read-only code that you can't modify because it relies on constantly mutating global state and is impossible to debug.

Raere
Dec 13, 2007

Can anyone recommend a guide for creating a GUI in Powershell? I want to make a frontend for a few of my scripts to make them easier for end users.

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Raere posted:

Can anyone recommend a guide for creating a GUI in Powershell? I want to make a frontend for a few of my scripts to make them easier for end users.
Most of the code you see out there for making GUIs in PowerShell uses Windows forms and it has you creating windows through code. Nothing wrong with that, if not a bit verbose, but I've always wanted to try creating a GUI in PowerShell with XAML. I've seen this post before but I've still never played with it myself. I like the idea of designing the windows in a GUI and then just taking that XML and using it.

If you try this, let me know how it works out.

Raere
Dec 13, 2007

Briantist posted:

Most of the code you see out there for making GUIs in PowerShell uses Windows forms and it has you creating windows through code. Nothing wrong with that, if not a bit verbose, but I've always wanted to try creating a GUI in PowerShell with XAML. I've seen this post before but I've still never played with it myself. I like the idea of designing the windows in a GUI and then just taking that XML and using it.

If you try this, let me know how it works out.

Thanks! I'm definitely going to take a look at it this week. I'm used to making GUIs with code in Python, but WYSIWYG is definitely going to be easier, even if there are additional steps to integrate it into the script. I will report back.

stubblyhead
Sep 13, 2007

That is treason, Johnny!

Fun Shoe

stubblyhead posted:

e: Actually I spoke too soon. I am able to connect directly to the instance, but going through a proxy appears to require https. A certificate is required to start an https listener, and I'm not sure a self-signed one will pass muster with my client (no CA in AWS we can use, and setting one up will probably get shot down as well). I think this is becoming more of an AWS question than a PS question, so I think I'll bow out at this point.

In the unlikely event anyone cares, the client didn't give a poo poo about self-signed certificates since these are short-lived servers by design. I actually just copied the Remote Desktop cert into Personal and skipped CA and CN checks. I hit a minor roadblock due to their proxy being a butthead, but switching the WinRM service to listen on 443 instead of 5986 took care of that. The powershell part to this was actually really simple, it was all the other layers that caused problems.

Hughmoris
Apr 21, 2007
Let's go to the abyss!

Ithaqua posted:

(get-content C:\temp\patient.txt) -match "^Location:+"

Output:
Location: ICU

Unless you want to find all of the patients where the location is ICU? Your question is poorly worded.

Sorry, the question was very poorly worded. Now that I my work laptop with me, here is my input file (with fake patient info):
code:
Patient:  WINTER, SNOW Facility:  X Location:  4W/248 Account Number:  1234567890  A  MRI ABDOMEN W/ CONTRAST has been ordered
blah blah blah
blah blah
blah blah blah
Patient:  TEST, APRIL1 Facility:  Y Location:  2E/244 Account Number:  9879878979  A  MRI ABDOMEN W/ CONTRAST has been ordered for this patient
An ideal output would be just the Location identifiers
code:
4W2
2E
How would you go about getting that? Once I can regex and capture the location identifier, I can do more work further in my script.

Hughmoris fucked around with this message at 02:36 on Jul 8, 2015

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Hughmoris posted:

Sorry, the question was very poorly worded. Now that I my work laptop with me, here is my input file (with fake patient info):
code:
Patient:  WINTER, SNOW Facility:  X Location:  4W2/248 Account Number:  1234567890  A  MRI ABDOMEN W/ CONTRAST has been ordered
blah blah blah
blah blah
blah blah blah
Patient:  TEST, APRIL1 Facility:  Y Location:  2E/244 Account Number:  0000581755113  A  MRI ABDOMEN W/ CONTRAST has been ordered for this patient
An ideal output would be just the Location identifiers
code:
4W2
2E
How would you go about getting that? Once I can regex and capture the location identifier, I can do more work further in my script.

I think I would do something like this:

code:
$Locations = Get-Content C:\temp\patient.txt | ForEach-Object {
    if ($_ -match 'Location:\s+(?<Location>[^/]+)') {
        $Matches['Location']
    }
 }
$Locations will contain an array of the locations. Of course instead of putting those in a variable, you could just do your work inside the if statement within the loop. However you want to handle it really.

RegEx breakdown:

  • \s+ matches 1 or more whitepsace characters.
  • () parentheses starts a capturing group. Here I'm using a named capture group by using (?<Name>pattern) to make it easier to refer to later.
  • [^/]+ this matches 1 or more characters that aren't a forward slash, and since this is the entire pattern inside the capture group, this is what will get stored.

Then it's just a matter of using the $Matches object and referring the capture group, either by number, or in this case by name.

You could do multiple capture groups (with multiple names) all in the one match, and refer back to all of them, so you could break down each line of this file into its components if you wanted (patient, location, facility, etc.).

Briantist fucked around with this message at 02:40 on Jul 8, 2015

Hughmoris
Apr 21, 2007
Let's go to the abyss!

Briantist posted:

I think I would do something like this:

code:
$Locations = Get-Content C:\temp\patient.txt | ForEach-Object {
    if ($_ -match 'Location:\s+(?<Location>[^/]+)') {
        $Matches['Location']
    }
 }
$Locations will contain an array of the locations. Of course instead of putting those in a variable, you could just do your work inside the if statement within the loop. However you want to handle it really.

RegEx breakdown:

  • \s+ matches 1 or more whitepsace characters.
  • () parentheses starts a capturing group. Here I'm using a named capture group by using (?<Name>pattern) to make it easier to refer to later.
  • [^/]+ this matches 1 or more characters that aren't a forward slash, and since this is the entire pattern inside the capture group, this is what will get stored.

Then it's just a matter of using the $Matches object and referring the capture group, either by number, or in this case by name.

You could do multiple capture groups (with multiple names) all in the one match, and refer back to all of them, so you could break down each line of this file into its components if you wanted (patient, location, facility, etc.).

Thanks for this.

Hadlock
Nov 9, 2004

Where $Airspace.jets is a .net ArrayList ($Airspace.jets = New-Object System.Collections.ArrayList)
And Rand777 is a constructor for an example object with two randomized [int]properties

Why is this valid with double parenthesis

$Airspace.jets.add((Rand777))

But with single parenthesis, it fails?

$Airspace.jets.add(Rand777)

I'm happy I have a solution, but I'm curious about why I have to encapsulate Rand777 a second time in parenthesis. I know that the first set are required as a parameter for the add method, but if I set $newjet = Rand777, this works, and for about an hour I was just doing

code:
$a = Rand777; $Airspace.jets.add($a)
But that's not very elegant. If I can assign a variable to the output of a function, why do I need to encapsulate the output of a function before passing it to the add() method? Is it because ArrayList isn't wrapped in the warmfuzzy Powershell cloak and I'm calling it directly and thus needs to be handled with kid gloves? Whereas powershell will generally be very forgiving with syntax? Thanks.

error messag
code:
At line:104 char:20
+ $Airspace.jets.add(Rand777)
+                    ~
Missing ')' in method call.
At line:104 char:20
+ $Airspace.jets.add(Rand777)
+                    ~~~~~~~
Unexpected token 'Rand777' in expression or statement.
At line:104 char:27
+ $Airspace.jets.add(Rand777)
+                           ~
Unexpected token ')' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

Hadlock fucked around with this message at 10:12 on Jul 9, 2015

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Hadlock posted:

Where $Airspace.jets is a .net ArrayList ($Airspace.jets = New-Object System.Collections.ArrayList)
And Rand777 is a constructor for an example object with two randomized [int]properties

Why is this valid with double parenthesis

$Airspace.jets.add((Rand777))

But with single parenthesis, it fails?

$Airspace.jets.add(Rand777)

I'm happy I have a solution, but I'm curious about why I have to encapsulate Rand777 a second time in parenthesis. I know that the first set are required as a parameter for the add method, but if I set $newjet = Rand777, this works, and for about an hour I was just doing

code:
$a = Rand777; $Airspace.jets.add($a)
But that's not very elegant. If I can assign a variable to the output of a function, why do I need to encapsulate the output of a function before passing it to the add() method? Is it because ArrayList isn't wrapped in the warmfuzzy Powershell cloak and I'm calling it directly and thus needs to be handled with kid gloves? Whereas powershell will generally be very forgiving with syntax? Thanks.

error messag
code:
At line:104 char:20
+ $Airspace.jets.add(Rand777)
+                    ~
Missing ')' in method call.
At line:104 char:20
+ $Airspace.jets.add(Rand777)
+                    ~~~~~~~
Unexpected token 'Rand777' in expression or statement.
At line:104 char:27
+ $Airspace.jets.add(Rand777)
+                           ~
Unexpected token ')' in expression or statement.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : MissingEndParenthesisInMethodCall

It's not because it's a .Net object, it also happens when you are assigning property values for cmdlets. It's looking for a value or object, sometimes of a specific type. The expression you've given is interpreted as a value.

Wrapping the expression in parentheses evaluates it and then returns the value of it.

Similarly, assigning to variable first (using = ) triggers the evaluation too, so the variable then contains the result, and you can use it directly.


For example (excuse the redundancy):

code:
Get-Date -Date Get-Date    # Error
Get-Date -Date (Get-Date) # OK

$date = Get-Date
Get-Date -Date $date    # OK

Get-Date -Date [DatetTime]::Now    # Error
Get-Date -Date ([DateTime]::Now)   # OK

Get-Date -Date Get-Content -Path C:\Windows\Dates.txt | Select-String -Pattern '\d\d_\d\d_\d\d' | Where-Object { $_.SOmething -eq 'Whatever' }   # Error
Get-Date -Date (Get-Content -Path C:\Windows\Dates.txt | Select-String -Pattern '\d\d_\d\d_\d\d' | Where-Object { $_.SOmething -eq 'Whatever' })   # OK
You get the idea.

UberJumper
May 20, 2007
woop
Okay i have a super weird bug, i am trying to uninstall a previous version of our software through powershell:

code:
Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "FooBar" } | ForEach-Object { $._Uninstall() }
This works and it will properly uninstall our software however it also removes the Microsoft Visual C++ 2010 Redistributable and 2008 Redistributables along with it. However if i uninstall it through add and remove programs it won't remove the VC++ redistributables. I am really puzzled here. Our installer is a super simple WiX installer, we don't package ether of these redistributables with our software.

Any ideas?

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

UberJumper posted:

Okay i have a super weird bug, i am trying to uninstall a previous version of our software through powershell:

code:
Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "FooBar" } | ForEach-Object { $._Uninstall() }
This works and it will properly uninstall our software however it also removes the Microsoft Visual C++ 2010 Redistributable and 2008 Redistributables along with it. However if i uninstall it through add and remove programs it won't remove the VC++ redistributables. I am really puzzled here. Our installer is a super simple WiX installer, we don't package ether of these redistributables with our software.

Any ideas?
You're probably not going to like this, but you should really avoid using Win32_Product at all costs. Usually you'll see articles like this one that talk about how bad it is to use it in WMI filters in group policy, but a lot of the same reasons apply to using it in PowerShell, or any context at all really.

The worst one is that it validates/reconfigures every installed package on the system which is not only slow, it can lead to weird behavior.

I don't know for sure why you're seeing what you're seeing, but since you should do this another way anyway, you might as well focus on how to do that and then see if you're still having the same issue.

The article I linked to has, from what I've seen, the best solution for listing packages: register your own WMI class using the registry provider. The only problem is that you need a MOF file (provided) and then need to register it on every machine where you want to use it, but since your code isn't using -ComputerName I assume you could script that part too. The neat thing is, once the WMI class is registered on the machine, you can use WMI remotely to query it from then on, and it's way faster.

Once you do that, I'm not sure yet how to programmatically remove it, other than just shelling out to msiexec.

I think my co-worker might have come up with a way; I'm going to ask him and report back. Edit: he doesn't remember, it was a one-off thing, so it probably just used msiexec and we ended up using group policy instead of the script anyway.

Briantist fucked around with this message at 00:48 on Jul 10, 2015

Swink
Apr 18, 2006
Left Side <--- Many Whelps
Unrelated but does that win32_product weirdness also apply when running 'wmic product' from the command line?

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Swink posted:

Unrelated but does that win32_product weirdness also apply when running 'wmic product' from the command line?
Yes, as far as I know any invocation of the WMI provider will cause it. It should be evident by the messages in the event log that show that it's validating or reconfiguring all the packages.

Venusy
Feb 21, 2007

UberJumper posted:

Okay i have a super weird bug, i am trying to uninstall a previous version of our software through powershell:

code:
Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -match "FooBar" } | ForEach-Object { $._Uninstall() }
This works and it will properly uninstall our software however it also removes the Microsoft Visual C++ 2010 Redistributable and 2008 Redistributables along with it. However if i uninstall it through add and remove programs it won't remove the VC++ redistributables. I am really puzzled here. Our installer is a super simple WiX installer, we don't package ether of these redistributables with our software.

Any ideas?

You're using -match to filter the name, so if $FooBar contains some part of the name of the VC++ packages (e.g. "dist"), they'll be counted as well. I've done this previously by uninstalling based on the GUID:
code:
Get-WMIObject Win32_Product -Filter 'IdentifyingNumber = "{1b411fd7-6f64-4410-a238-9cf97be561da}"'
But I didn't realize how many problems Win32_Product could cause, so I'll stop doing that.

Swink
Apr 18, 2006
Left Side <--- Many Whelps
For the record, I have not seen any crazy reconfiguration of all packages when using wmic product. Nope Wmic product totally reconfigs every goddamn package which explains why it takes so long to return results.

I'm specifically using it for broken packages* that wont remove correctly so maybe it should still be avoided in normal scenarious.

New Q:

What is a simple option for source control? I want it to be free and private. Can I set up Git on Windows and sync it with Dropbox? I'd use GitHub for Windows except I dont want to pay for a private repository at this stage.



*Java

Swink fucked around with this message at 12:21 on Jul 15, 2015

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Swink posted:

For the record, I have not seen any crazy reconfiguration of all packages when using wmic product. I'm specifically using it for broken packages* that wont remove correctly so maybe it should still be avoided in normal scenarious.

New Q:

What is a simple option for source control? I want it to be free and private. Can I set up Git on Windows and sync it with Dropbox? I'd use GitHub for Windows except I dont want to pay for a private repository at this stage.



*Java

Visual Studio Online

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Swink posted:

What is a simple option for source control? I want it to be free and private. Can I set up Git on Windows and sync it with Dropbox? I'd use GitHub for Windows except I dont want to pay for a private repository at this stage.
Use Git.

Remember that GitHub is just a place online to store it. Git runs locally, and your repo is stored within the directory, so you should be able to sync it with dropbox, though it is helpful to use some central place like github. If you want private but don't want to pay, check out BitBucket from Atlassian.

GitHub for Windows is just a graphical Git client. You don't have to use that to use GitHub. Atlassian also makes a graphical client called SourceTree

Visual Studio (later versions) have git support built-in; it works quite well.

Unless I'm using VS, I typically will use command line git, even on Windows.

If you need a quick primer on Git, I like Atlassian's online tutorials but GitHub has one too.

hihifellow
Jun 17, 2005

seriously where the fuck did this genre come from

Briantist posted:

Win32_Product is bad

So I was using this for software uninstalls, and after all the posts here I went looking for alternatives and found the regkey HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall which lists everything installed on the system by UID along with the uninstall string, which if it's an MSI is just "MSIEXEC /X{UID}". Was easy to script and faster to boot, since everything I'm working with is installed via MSI anyway.

Swink
Apr 18, 2006
Left Side <--- Many Whelps
Right, cool. I was unfamiliar with the relationship with git, github and other services like bitbucket.

I'm familiarish with command line Git so I'm happy I can just use that. Private repos on BitBucket is handy.


hihifellow posted:

So I was using this for software uninstalls, and after all the posts here I went looking for alternatives and found the regkey HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall which lists everything installed on the system by UID along with the uninstall string, which if it's an MSI is just "MSIEXEC /X{UID}". Was easy to script and faster to boot, since everything I'm working with is installed via MSI anyway.

This is the way I usually enumerate and unininstall programs.

Swink fucked around with this message at 12:06 on Jul 15, 2015

Swink
Apr 18, 2006
Left Side <--- Many Whelps
Double post but I wanted to seperate this out.

I'm trying to focus on abstraction and parametising the stuff I write so I can be a ~good coder~. I'm having trouble figuring out when and why its necessary on some scripts.

Take this script I wrote to warn users of an impending password expiry. It's going to run every day or so as a scheduled task. How could I improve it? What could I gain by improving it? Is this an example of a script that doesnt really need to be anything more than it is?



code:
$alerttime = (get-date).adddays(10)

$alertusers = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -SearchBase "OU=users,DC=domain,DC=com" `
 –Properties "samaccountname","DisplayName","emailAddress", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "samaccountname","Displayname","emailAddress",@{Name="ExpiryDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | where {$_.expirydate -le $alerttime }  

 foreach ($user in $alertusers){
        
         $emailBody = "<html>

	        <body>
		        <p>
			        <span style='font-family:arial,helvetica,sans-serif;'>Hi " + $user.Displayname + ",</p>
		      Your password will expire at  " + $user.ExpiryDate  + ". You must change your password soon.
	        </body>
        </html>"

 Send-MailMessage -From [email]Isupport@company.com[/email] -Subject "Password Expiry Reminder" -To $user.emailAddress -Body $emailBody -BodyAsHtml -SmtpServer smtp.server.com
 }

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug
Can't you just configure the group policy that notifies users literally every time they log in that their AD password is going to expire in [X] days?

vanity slug
Jul 20, 2010

Ithaqua posted:

Can't you just configure the group policy that notifies users literally every time they log in that their AD password is going to expire in [X] days?

What if they're not always using their laptop / desktop?

12 rats tied together
Sep 7, 2006

Swink posted:

Take this script I wrote to warn users of an impending password expiry. It's going to run every day or so as a scheduled task. How could I improve it? What could I gain by improving it? Is this an example of a script that doesnt really need to be anything more than it is?

The only way this could possibly be "improved" would be if you turned the actual get-and-check-and-email part into a function that accepts a username, checks to see if their password is going to expire soon, and then sends them an email. This gives you a little bit more control in that you can run the script against only some users, only one user whenever you want, etc.

It also removes the need for you to have a searchbase. Since, at some point in the future, the location of your users may change OUs, having a hardcoded script isn't as flexible as having just a function for "user object -> password expiration?". You can handle the actual searching and selection of user objects outside of the function itself, so your scheduled task would be "passwordcheck_allusers.ps1" and inside the script you would have your function definition (or the import of your module that contains this function), some brief powershell that grabs the users you want to check and then pipes them into a ForEach loop that runs the actual function.

This isn't the greatest example for a couple of reasons, like that the functionality already exists in group policy and that the actual parameters of your script (where to look, who to check) aren't likely to change. But, the philosophy of turning "script that does things" into "script that provides input to a more general function that does things" could be useful in the future. You could separate it out even further by removing the send-email part of the script, so you just have a function that "returns an object containing html output for a password expiration notice", but that is potentially not worthwhile since you are (for now) almost always going to just be sending an email. In the end it comes down to a judgement call based on the context of the situation and the complexity of the script.

In general though, for powershell administration tasks, I strongly recommend having all of your scripts (even those intended to be tasks) be callable at any point from the shell against one-off cases. It's great to have check_all_drives.ps1 that gets all the users and filters by x,y, and z and then checks all of their mapped drives and compares them to a list that you specify and compiles an email and sends it to everyone in sysadmins and sends you a confirmation that the script worked or not, but that's not very useful if you just need to get Jeff from Finance's mapped drives real quick to check something. So, it would be better if you just had Get-MappedDrive -user Jeff and a scheduled task that does some bigger poo poo with Get-MappedDrive if necessary, if that makes any sense.

Briantist
Dec 5, 2003

The Professor does not approve of your post.
Lipstick Apathy

Swink posted:

Double post but I wanted to seperate this out.

I'm trying to focus on abstraction and parametising the stuff I write so I can be a ~good coder~. I'm having trouble figuring out when and why its necessary on some scripts.

Take this script I wrote to warn users of an impending password expiry. It's going to run every day or so as a scheduled task. How could I improve it? What could I gain by improving it? Is this an example of a script that doesnt really need to be anything more than it is?



code:
$alerttime = (get-date).adddays(10)

$alertusers = Get-ADUser -filter {Enabled -eq $True -and PasswordNeverExpires -eq $False} -SearchBase "OU=users,DC=domain,DC=com" `
 –Properties "samaccountname","DisplayName","emailAddress", "msDS-UserPasswordExpiryTimeComputed" | Select-Object -Property "samaccountname","Displayname","emailAddress",@{Name="ExpiryDate";Expression={[datetime]::FromFileTime($_."msDS-UserPasswordExpiryTimeComputed")}} | where {$_.expirydate -le $alerttime }  

 foreach ($user in $alertusers){
        
         $emailBody = "<html>

	        <body>
		        <p>
			        <span style='font-family:arial,helvetica,sans-serif;'>Hi " + $user.Displayname + ",</p>
		      Your password will expire at  " + $user.ExpiryDate  + ". You must change your password soon.
	        </body>
        </html>"

 Send-MailMessage -From [email]Isupport@company.com[/email] -Subject "Password Expiry Reminder" -To $user.emailAddress -Body $emailBody -BodyAsHtml -SmtpServer smtp.server.com
 }

I do think it's beneficial to abstract some of the hardcoded values away. Besides being good practice, it makes it much easier to change things around when the environment changes. Even if you just hardcode the values into variables at the top of the script, at least everything you could have to change is in one place. Things that could change:
  • Alert time (might want to adjust this)
  • AD Domain (seems unlikely, but you never know)
  • HTML template (consider using pre-defined tokens and doing a replace)
  • All of the email parameters

It might seem silly to make these into parameters for this script because it's called by a task, but consider that it makes debugging/testing much easier (especially if you use -WhatIf), let's you use the script in other environments more easily (or will you be working at this company until you retire/die?), and still allows for sensible defaults and validation.

So yeah, I went a little crazy here:

code:
[CmdletBinding(SupportsShouldProcess=$true)]
param(
    [Parameter()]
    [ValidateRange(1,([int]::MaxValue))]
    [Alias('Days')]
    [int]
    $DaysBefore = 10 ,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [Alias('ADDN')]
    [String]
    $ActiveDirectoryDN = (Get-ADDomain).DistinguishedName ,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [String]
    $UserDN = "OU=users" ,

    [Parameter()]
    [ValidatePattern('^[^@]+@[^@]+$')] # not the best email matching regex
    [Alias('From')]
    [String]
    $FromEmail = 'Isupport@company.com',

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [Alias('Subject')]
    [String]
    $EmailSubject = 'Password Expiry Reminder' ,

    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [String]
    $SmtpServer = 'smtp.server.com'
)

$emailTemplate = @"
<html>
    <body>
        <p>
            <span style='font-family:arial,helvetica,sans-serif;'>Hi @@DisplayName@@,
        </p>
        Your password will expire at @@ExpiryDate@@. You must change your password soon.
    </body>
</html>
"@

$mailParams = @{
    BodyAsHtml = $true
    From = $FromEmail
    Subject = $EmailSubject
    SmtpServer = $SmtpServer
}

$alerttime = (Get-Date).AddDays($DaysBefore)

$alertusers = Get-ADUser -Filter { (Enabled -eq $True) -and (PasswordNeverExpires -eq $False) } -SearchBase "$UserDN,$ActiveDirectoryDN" –Properties "samaccountname","DisplayName","emailAddress", "msDS-UserPasswordExpiryTimeComputed" | 
Add-Member -MemberType ScriptProperty -Name ExpiryDate -Value { [datetime]::FromFileTime($this."msDS-UserPasswordExpiryTimeComputed") } -Force -PassThru | 
Where-Object { $_.ExpiryDate -and $_.ExpiryDate -le $alerttime } |
ForEach-Object {
    $user = $_

    $emailBody = $emailTemplate
    $user.PSObject.Properties | ForEach-Object {
        $emailBody = $emailBody -ireplace "@@$([RegEx]::Escape($_.Name))@@",$user."$($_.Name)"
    }

    $mailParams.Body = $emailBody
    $mailParams.To = $user.emailAddress

    if ($PSCmdlet.ShouldProcess($user.DisplayName)) {
        Send-MailMessage @mailParams
    } else {
        Write-Verbose "Would have sent expiry message for user '$($user.DisplayName)' to '$($mailParams.To)' via '$($mailParams.SmtpServer)'."
        Write-Verbose "Message body:"
        Write-Verbose $mailParams.Body
    }
}
Maybe the HTML template part was a little overboard, but it lets you use @@PropertyName@@ in the template for any property that will exist in the user object and not have to change the code.

You can call this with -WhatIf and it won't send an email (use -Verbose to see the actual email contents).

So now this is parameterized, to the point where hopefully anyone reading this can use it.

I would probably go further and add a parameter for the email To address, useful for overriding it during testing (send it to myself instead of the end user), maybe make a few other tweaks, but meh.

Adbot
ADBOT LOVES YOU

Swink
Apr 18, 2006
Left Side <--- Many Whelps
Good responses, thanks all.


Ithaqua posted:

Can't you just configure the group policy that notifies users literally every time they log in that their AD password is going to expire in [X] days?

The answer to this was already put forward, but I've had the CEO overseas with only his iPad. He got locked out and had to wait until he was back in local business hours to get support. The email is specifically for that scenario. Plus stacks of users just dont heed the popup.


Briantist, you just demonstrated supreme aptitude. The HTML template has applications to some other god-awful scripts I have in place. For me and my current org I don't see the value of parametrizing that particular script in this particular scenario. You've left me with a great example for the future though.

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