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
adaz
Mar 7, 2009

Powershell is an Open source, cross platform, pipelined, object oriented scripting language first introduced by Microsoft way back in 2002.

Powershell what?
Windows Powershell was built as the next generation windows shell & scripting language, designed to replace the venerable command line AND vbscript. It's a bit of an odd beast in that it combines unixy shell syntax with the .NET architecture. It's also the worlds (only) object oriented pipeling language which lets you do some really neat things.

Microsoft has since used it as the preferred method for administrating exchange & SQL Server 2008 onwards, it's also the "official" scripting language nowadays, replacing vbscript. Since V5 it has also been open sourced and made cross platform (Linux, Windows, Mac Os x)

Major Features

- Cross Platform: Runs on Mac os x, Linux, and Windows
- Open Source: MIT Licensed
- Support for Desired State
- Pipeline - just like Bash powershell support chaining commands using hte | operator. Unlike bash, powershell lets you send complex objects between commands letting you build arbitrarily complex workflows using little bits of functionality
- .NET Framework Based- under the hood powershell runs on the .NET Framework which is the general purpose programming VM that languages such as C#, VB.NET, and F# sure (comparable to the Java Virtual Machine) which gives you immediate access to a vast API


Why do I care
Because it's so easy to interface with .NET powershell is incredibly powerful, from a scripting standpoint, compared to VBscript & bash. You can do crazy things in powershell that you'd never dream of in vbscript or bash. IF you're a windows system admin type you should be using powershell now, it's just so drat easy compared to vbscript & other tools you might have used in the past.


Gimme some code examples
Sure! This is the easiest way to see what powershell does rather than have me fill up pages on what it can do.

Retrieve a dataset from a SQL Server

code:

### Required to reference from inside the script.
$SQLDT = New-Object "System.Data.DataTable"

############
# SQL Data #
############
function Get-SqlData
{
    param([string]$serverName=$(throw 'serverName is required.'), [string]$databaseName=$(throw 'databaseName is required.'),
          [string]$query=$(throw 'query is required.'))
    $sqldt.reset()
    $connString = "Server=$serverName;Database=$databaseName;Integrated Security=SSPI;"
    $da = New-Object "System.Data.SqlClient.SqlDataAdapter" ($query,$connString)
    [void]$da.fill($SQLDT)
}
Example: Get-SqlData -server istsql -database istsql -query "select lanid from adusers where email like '%.gov%'

This would return a sqldataset from your sql server of all emails that are like .gov. You could then do something like this:

code:
for($i=0;$i -lt $sqldt.rows.count;$i++) {
  $sqldt.rows[$i].lanid # do something with the result

}
Do a AD LDAP query!
code:
############
# Get-user #
############
function Get-User($email)
{
        # this function should be passed the email of the user to be returned
        $root = [ADSI] "LDAP://yourdomain.com"
        $searcher = New-Object System.DirectoryServices.DirectorySearcher $root
        $searcher.filter = "(&(objectCategory=person)(objectClass=user)(mail=$email))"
        $user = $searcher.FindOne()
        $Script:ADUser = $user.GetDirectoryEntry()
}
Example: Get-User -email user@contoso.com

Reset a password
code:
$password = "PassW0rd"
$Aduser.psbase.Invoke("SetPassword",$password)
$Aduser.psbase.CommitChanges()
Modify a AD property

code:
 $Aduser.Description = "whatever the you want!"
 $aduser.setinfo()
Create a new email from a template using COM & Outlook
code:
$OutlookApp = New-Object -COM Outlook.Application
$OutlookMapiNamespace = $OutlookApp.GetNamespace("mapi")
$OutlookInbox = $OutlookMapiNamespace.GetDefaultFolder("OLFolderInbox")

$NewMail = $OutlookApp.CreateItemFromTemplate($FileLocation,$OutlookInbox)
[void]$NewMail.Display()
Bind to an AD Object & Retrieve all its properties
code:
$Userobj = [adsi]LDAP://domain.com/CN=user,ou=blah,ou=blah,dc=blah,dc=local
$UserObj | Get-Member
.NET Framework ahoy!
Here we'll create a menu of options your user's can choose from:
code:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing") 

#################
# Create-UIlist #
#################
Function Create-UIList {
param ($Title,$ChoiceQuestion,$Choices)
# Create our form
$MainForm = New-Object System.Windows.Forms.Form 
$MainForm.Text = $Title
$MainForm.Size = New-Object System.Drawing.Size(500,200) 
$MainForm.StartPosition = "CenterScreen"

# Key Handlers for enter & Escape
$MainForm.KeyPreview = $True
$MainForm.Add_KeyDown({if ($_.KeyCode -eq "Enter") 
    {$Script:UIListResult=$ListBox.SelectedItem;$MainForm.Close()}})
$MainForm.Add_KeyDown({if ($_.KeyCode -eq "Escape") 
    {$MainForm.Close()}})

# OK button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,120)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$Script:UIListResult=$ListBox.SelectedItem;$MainForm.Close()})
$MainForm.Controls.Add($OKButton)

# Cancel Button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,120)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.Add_Click({$MainForm.Close()})
$MainForm.Controls.Add($CancelButton)

# Label
$objLabel = New-Object System.Windows.Forms.Label
$objLabel.Location = New-Object System.Drawing.Size(10,20) 
$objLabel.Size = New-Object System.Drawing.Size(480,20) 
$objLabel.Text = $ChoiceQuestion
$MainForm.Controls.Add($objLabel) 

# Our list box
$ListBox = New-Object System.Windows.Forms.ListBox 
$ListBox.Location = New-Object System.Drawing.Size(10,40) 
$ListBox.Size = New-Object System.Drawing.Size(460,20) 
$ListBox.Height = 80

# Choices for list box
for ($i=0;$i -lt $Choices.Count; $i++) {
[void]$ListBox.Items.Add($Choices[$i])
}


# Add controls for Form & Show it.
$MainForm.Controls.Add($ListBox) 
$MainForm.Topmost = $True
$MainForm.Add_Shown({$MainForm.Activate()})
[void]$MainForm.ShowDialog()
}

$choicesArr = New-Object System.Collections.ArrayList
[void]$ChoicesArr.Add("Do Something")
[void]$ChoicesArr.Add("DO something PRETTY")
[void]$ChoicesArr.Add("DO something else!")

Create-UIList -title "This is a choices list" -choicequestion "please select an option to perform" -choices $choicesarr
To handle the result:

code:
switch ($UIListresult) {
   {$_ -eq "Do Something"} { WE do something here;}
   {$_ -eq "DO something PRETTY"} {DO something PRETTY;}
   {$_ -eq "DO something else!"} { more stuff;}
      
}

Add user to AD group based on text file

code:
$domain = "contoso.com" # your domain here
$content = Get-Content "c:\temp\pathtotextfile.txt"
$userou = "ou=users,ou=yourdomain,dc=blah,dc=com" # your user OU
$GroupToAdd = [adsi]"LDAP://yourdomain.com/CN=YourGroup,OU=groupOU,DC=YourDomain,DC=Com"
#loop through & add user to group.
Foreach ($lanid in $content) {
   $UserDN = "LDAP://$Domain/cn=$lanid,$UserOU"
   GrouptoAdd.Add($UserDN)
}
Questions?
Post in the thread!

Resources/Links

Powershell source code https://github.com/PowerShell/PowerShell the underlying code for powerhsell

Hey, scripting guy blog - https://devblogs.microsoft.com/scripting/ Fantastic resource updated daily with lots of explanation & real life problems.

Powershell team Blog - [url]http://blogs.msdn.com/powershell/default.aspx[/url

Channel 9 (Microsoft Video channel - https://channel9.msdn.com/Series/GetStartedPowerShell3 - getting started with powershell

Powershell.org - https://powershell.org/ nonprofit that posts articles, etc about powershell and devops.

adaz fucked around with this message at 21:18 on Jan 28, 2020

Adbot
ADBOT LOVES YOU

adaz
Mar 7, 2009

I feel kind of bad skipping a lot of the basic syntax/grammar of powershell but if you really want to learn how it works I'll be more than happy to help you out. Know that 3 basic commands really take care of most of you problems:


code:

Command | get-member

Retrieves all the methods & Properties of an object


code:
Get-Command WhateverYou'reSearchingFor
Retrieves the built in commandlet, if it exists. Use * for wildcard searches.

code:
Get-Help Cmndlet
Get-Help is golden. Think of it as a Man command for those who come from a unix background.

adaz fucked around with this message at 21:17 on Jan 28, 2020

skipdogg
Nov 29, 2004
Resident SRT-4 Expert

I'll be following this like a hawk. I need to learn powershell badly, but everytime I bust out the book to try to figure something out I just revert back to a batch file to do it. I have a hell of a time with any programming type syntax, just makes no sense to me.

adaz
Mar 7, 2009

skipdogg posted:

I'll be following this like a hawk. I need to learn powershell badly, but everytime I bust out the book to try to figure something out I just revert back to a batch file to do it. I have a hell of a time with any programming type syntax, just makes no sense to me.

Next time you have a problem that you want to use a batch/vbscript file for post it here, can show you how it's done in powershell.

skipdogg
Nov 29, 2004
Resident SRT-4 Expert

adaz posted:

Next time you have a problem that you want to use a batch/vbscript file for post it here, can show you how it's done in powershell.

Well I actually cheat and use a program called Batch-O-Matic most of the time.

Here's a situation I would be interested in figuring out, and you'll probably laugh at how simple it is, but like I mentioned, I suck at this.

I have a roaming profile server, there are about 700 directories in there. I need to delete a folder in each one of them for a program we no longer use, it would free up some space and reduce the size of my backup set a ton. Each folder is about 80 megs.

I have a text file with the name of each folder on a separate line, but I guess my disconnect is how to get the command to loop and substitute the name in the next line of the file.

So what I need to do is delete a folder 'Spark' inside of each users directory.

\\roamserver\profiles\%username%\ntprofile\Spark


Normally I would use the old batch o matic, but I need to learn to do this the right way and not rely on questionable programs off the internets.

So I know the command to delete the folder would be
code:
Remove-Item \\roamserver\profiles\%username%\ntprofile\Spark -recurse
But how do I get the list of 700 variables for %username% to feed into it, and repeat until done? Maybe with a log file output saying \\roamserver\profiles\%username%\ntprofile\Spark deleted successfully? That would be icing on the cake.

Dessert Rose
May 17, 2004

awoken in control of a lucid deep dream...
This will do what you want (untested):

code:
foreach ($i in Get-ChildItem \\roamserver\profiles) {
  Remove-Item $i\ntprofile\Spark -recurse
}
You may have to tack \\roamserver\profiles\ on there before the $i.

Here is a page on loops in powershell that I used to recall syntax. It should give you an idea of what's possible. You're probably going to use foreach more than any other loop structure in system admin.

To add the logging functionality just tack an echo statement or whatever you prefer in that loop. $i is a Directory object that you can do all sorts of fun things with.

Dessert Rose fucked around with this message at 21:08 on Mar 31, 2010

adaz
Mar 7, 2009

skipdogg posted:

:words:

This is exactly the type of problem powershell was designed to handle.

The easiest way to do this is I assume everything under \\roamserver\profiles needs that directory deleted right? If that's the case!

code:
$Dirs = Get-Item \\roamserver\profiles\*

Foreach($Dir in $Dirs) {
  if($Dir.PsIsContainer -match $true) {
     try {
      Remove-Item "$Dir.Fullname\ntprofile\Spark" -recurse
      Log-Event -eventtype "action" -event "removed $Dir.Fullname\ntprofile\Spark"
    } catch {
      Log-Event -eventtype "error" -event "unable to remove $Dir.Fullname\ntprofile\Spark ! Error was: $($error[0].exception)"
    }
  }
}

################################
# Log-Event function I use a lot
# not strictly needed 
################################
$Logfile = "C:\Temp\removefolders.log"
Function Log-Event{
param([string]$EventType,[string]$event)
$time = Get-Date

switch($eventType)
    {
        {$_ -match "Error"} {
            $Output = "!!!!  $time    $Event"
            Write-Warning $Output
            Out-File -FilePath $logFile -Append -InputObject $Output
            }
        {$_ -match "Informational"} {
            $Output = "----  $time   $Event"
            Out-File -FilePath $logFile -Append -InputObject $Output
            }
        {$_ -match "Action"} {
            $Output = "++++  $time    $Event"
            Write-Host $Output -ForegroundColor Black -BackgroundColor White
            Out-File -FilePath $logFile -Append -InputObject $Output
            }
        default { 
            $Output = "----  $time    $Event"
            Out-File -FilePath $logFile -Append -InputObject $Output
            }
    }

}
Ryouga's solution also works, although it won't log and will throw errors if the directory doesn't exist. His is the more "quick and elegant" solution :)

Line by line explanation:

code:
$Dirs = Get-Item \\roamserver\profiles\*
This grabs all the directories & files underneath \\roamserver\profiles and puts them into the $Dirs object (which is actually an array). You can see all the fun stuff you can do with the $Dirs by doing $Dirs | get-Member which will list all the properties & methods on the Dirs object if you're interested.

code:
Foreach($Dir in $Dirs) {
Start of our loop. Foreach is, as Ryouga said, the loop you'll probably find yourself using the most. What it does is iterate through the entire array/collection and we'll use the $Dir object to reference the current item in the array/collection we're on.

code:
  if($Dir.PsIsContainer -match $true) {
Standard If statement in powershell. Stritcly speaking not needed, but this will prevent the script from trying to delete any files and makes sure the item being processed is a directory.

code:
     try {
Again, not really needed this is a Try/Catch block for logging purposes. It executes everything in try unless an error is thrown in which case it executes the code in the Catch block

code:
      Remove-Item "$Dir.Fullname\ntprofile\Spark" -recurse
remove-item that we already went over.

code:
      Log-Event -eventtype "action" -event "removed $Dir.Fullname\ntprofile\Spark"
Here we're calling the Function Log-Event and telling it the eventtype & what to log. pretty self explanatory.

code:
    } catch {
      Log-Event -eventtype "error" -event "unable to remove $Dir.Fullname\ntprofile\Spark ! Error was: $($error[0].exception)"
    }
If any errors are thrown when trying to delete it this will write an event log and the error details using the $Error variable which is a built in to powershell. $Error[0] is the way you index into the first element of an array (in this case the last error that was thrown), $error[1] would be the second object, etc.

adaz fucked around with this message at 22:05 on Mar 31, 2010

skipdogg
Nov 29, 2004
Resident SRT-4 Expert

Thanks guys! I'll give this a whirl and see how it works. I really would like to automate terminating employees in AD somewhat ( I work in a high turnover environment ) but I want to try to give it a go before relying on you fine folks.

adaz
Mar 7, 2009

skipdogg posted:

Thanks guys! I'll give this a whirl and see how it works. I really would like to automate terminating employees in AD somewhat ( I work in a high turnover environment ) but I want to try to give it a go before relying on you fine folks.

Sure, I added some links in the OP to various resources on powershell. The Hey, Scripting Guy blog is a really good way to find real life examples with detailed explanations. They even have a link to the Powershell ScripTomatic which is probably close to that batchomatic tool you had been using (http://www.microsoft.com/downloads/details.aspx?FamilyID=d87daf50-e487-4b0b-995c-f36a2855016e&displaylang=en)

adaz fucked around with this message at 23:39 on Mar 31, 2010

Tont
Oct 1, 2004
Fear me, for I am Tont
I'm going to abuse the hell out of this thread in the months too come! Well, not really, I can usually find the help I need by using the resources you listed in the OP. But I'm having a hell of a time figuring this one out.

I need to get a list of all users in a domain that have the 'Log On To...' option defined in Active Directory. And, if possible, get a list of all of the machines each user is allowed to log on to. Then, preferably with a different script, I need to change everyone back to allowing all users to log onto all computers.

I think I'm having such a hard time finding help because when you search for anything with Log On To in the search you get a whole lot of listings for logon scripts and non-relevant logon related information.

Any help would be greatly appreciated.

adaz
Mar 7, 2009

Tont posted:

I'm going to abuse the hell out of this thread in the months too come! Well, not really, I can usually find the help I need by using the resources you listed in the OP. But I'm having a hell of a time figuring this one out.

I need to get a list of all users in a domain that have the 'Log On To...' option defined in Active Directory. And, if possible, get a list of all of the machines each user is allowed to log on to. Then, preferably with a different script, I need to change everyone back to allowing all users to log onto all computers.

I think I'm having such a hard time finding help because when you search for anything with Log On To in the search you get a whole lot of listings for logon scripts and non-relevant logon related information.

Any help would be greatly appreciated.

We don't use logon to around here and as you said searching for it's a bitch. I do what I always do when trying to find a obscure LDAP/AD setting - I set it up on an AD object and bind it to the object in powershell:

code:
$User = [adsi]"LDAP://cn=test,ou=users,ou=blah,dc=local,dc=com"
Then I did a $User | GM to see all the properties that were set on the object... and what is this?

$User.UserWorkstations - that's promising. I check out the MSDN article on it (http://msdn.microsoft.com/en-us/library/ms680868%28v=VS.85%29.aspx) and lo and behold this is exactly what we want. So now all we have to do is create a LDAP search syntax for you. LDAP Search Syntax Is the Best (tm). I usually start here:
http://msdn.microsoft.com/en-us/library/aa746475%28VS.85%29.aspx

I saved you some time and effort and here is the appropriate code:

code:
$root = [ADSI] "LDAP://your.domain"
$searcher = New-Object System.DirectoryServices.DirectorySearcher $root
$searcher.filter = "(&(objectClass=user)(userworkstations=*))"
$users = $searcher.FindAll()
this will return all the users in your domain who have the userworkstations attribute set. You can then iterate through them and do whatever you need:

code:
for($i=0;$i -lt $users.count;$i++) {
  $User = [adsi]"$($users[$i].path.tostring())" # had to add the ToString part, kept crashing my powershell.weird
  $user.putex("1","userworkstations","0")
  $user.setinfo()
  
}

adaz fucked around with this message at 18:33 on Apr 1, 2010

DreadCthulhu
Sep 17, 2008

What the fuck is up, Denny's?!
Powershell is a great tool and I support this thread. It's good to give it more exposure.

Tont
Oct 1, 2004
Fear me, for I am Tont

adaz posted:

We don't use logon to around here and as you said searching for it's a bitch. I do what I always do when trying to find a obscure LDAP/AD setting - I set it up on an AD object and bind it to the object in powershell:

code:
$User = [adsi]"LDAP://cn=test,ou=users,ou=blah,dc=local,dc=com"
Then I did a $User | GM to see all the properties that were set on the object... and what is this?

$User.UserWorkstations - that's promising. I check out the MSDN article on it (http://msdn.microsoft.com/en-us/library/ms680868%28v=VS.85%29.aspx) and lo and behold this is exactly what we want. So now all we have to do is create a LDAP search syntax for you. LDAP Search Syntax Is the Best (tm). I usually start here:
http://msdn.microsoft.com/en-us/library/aa746475%28VS.85%29.aspx

I saved you some time and effort and here is the appropriate code:

code:
$root = [ADSI] "LDAP://your.domain"
$searcher = New-Object System.DirectoryServices.DirectorySearcher $root
$searcher.filter = "(&(objectClass=user)(userworkstations=*))"
$users = $searcher.FindAll()
this will return all the users in your domain who have the userworkstations attribute set. You can then iterate through them and do whatever you need:

code:
for($i=0;$i -lt $users.count;$i++) {
  $User = [adsi]"$($users[$i].path.tostring())" # had to add the ToString part, kept crashing my powershell.weird
  $user.putex("1","userworkstations","0")
  $user.setinfo()
  
}

Thank you so much for this. I didn't get it to work like this, but it gave me all the information I needed to start digging a little more.

A lot of the examples I ended up finding online were using commands like get-qaduser and connect-qadservice. I found out these were from a free snapin by quest. http://www.quest.com/powershell/activeroles-server.aspx Download and run it. Then you need to add it by using the following command.
code:
Add-PSSnapin Quest.ActiveRoles.ADManagement
One of my main problems at first was simply getting powershell to reference the right child domain. Even when I ran powershell on a domain controller in the correct domain it would still only reference the parent. So I had to run this command.
code:
connect-qadservice child.domain.com
Then I started using the get-qaduser command and the userWorkstations properties that you found for me and eventually ended up with this command.
___________________________________________
get-qaduser -dontusedefaultincludedproperties -includedproperties 'UserWorkstations' -objectattributes @{'UserWorkstations'='*'} | format-list Name,userWorkstations
___________________________________________
That gave me a list of all users with the variable I was looking for. The -dontusedefaultincludedproperties was just to save processing time. The -includedproperties 'userWorkstations' command is because the default get-qaduser result list doesn't include userWorkstations. The -objectattributes @{'userWorkstations'='*'} switch makes it only return objects that have something defined in userWorkstations.

From there it was a simple task of assigning a variable to that search result and then piping it into the command to empty the userworkstations object.
___________________________________________
$fixitlist = get-qaduser -DontUseDefaultIncludedProperties -includedproperties 'userWorkstations' -objectattributes @{'UserWorkstations'='*'}
___________________________________________
$fixitlist | set-qaduser -objectattributes @{userworkstations=''}

___________________________________________
That did it for me. It changed all of my special snowflakes back to where I wanted them.

Thank you so much.

edit: my code snippets were breaking tables, so I removed them. Makes it a little harder to read.

Tont fucked around with this message at 00:21 on Apr 3, 2010

adaz
Mar 7, 2009

Weird, code should have worked I tested it out on our domain before I sent it out to you. Probably an error in DN syntax or something? Or are you using powershell v1? I don't think anything in there is v2 only but I never tested it on it.

And yeah, the Quest tools (there are others besides the AD ones) are pretty cool. I just try to avoid them since you can never be sure if the computer you're running your script on has them installed and they hide a lot of the backend stuff that can be helpful to know.

Glad you got it working :)

Victor
Jun 18, 2004
adaz, thanks for all the example scripts; may I steal them? Also, I would suggest adding Windows Powershell in Action &mdash it's a pretty good book written by someone from the inside.

I've written several PowerShell articles; here are a few that are probably more interesting:I kind of dove in intensely, got familiarized, wrote a bunch of articles (some of which are still very raw), and then moved on to other things.

adaz
Mar 7, 2009

Feel free Victor. And yes, powershell in action is an awesome book.

Also it's a good time to mention that it is the Microsoft sponsored 2010 scripting games! Pretty good time and you can win fabulous prizes (well, not really). A good way to get introduced to the language by giving you real world problems to solve

http://blogs.technet.com/heyscriptingguy/archive/2010/04/26/2010-scripting-games-all-links-on-one-page.aspx

e: also they introduced Powershell Visual Search on bing.com. Holy god it's awesome check it out: http://www.bing.com/visualsearch?g=powershell_cmdlets&FORM=Z9GE22

adaz fucked around with this message at 23:04 on Apr 26, 2010

The Diddler
Jun 22, 2006


I've just started trying to get into Powershell. I do a fair amount of scripting, with both VBScript and batch files. My problem is that I don't really have a problem right now that I need a new script for. Is there any benefit to replacing my current vbs and bat files with powershell scripts? Or is there anything flat-out awesome that it does, maybe a solution that I could find a problem for?

I can make it beep, but that isn't really useful. :sigh:

adaz
Mar 7, 2009

Bob Cthulhu posted:

I've just started trying to get into Powershell. I do a fair amount of scripting, with both VBScript and batch files. My problem is that I don't really have a problem right now that I need a new script for. Is there any benefit to replacing my current vbs and bat files with powershell scripts? Or is there anything flat-out awesome that it does, maybe a solution that I could find a problem for?

I can make it beep, but that isn't really useful. :sigh:

Replacing your current stuff with powershell won't do too much good other than teaching yourself how to use it. I mean, yes it's undoubtedly better than VBScript and Batch files for scripting, but without a problem to solve not sure what to tell you. I find it hard to explain all the benefits because there really are just so many. It's so much "cleaner" than vbscript and having access to the entire .NET framework is an incredible advantage.

If you're asking for what it flat out does better than vbscript the answer is "everything."

I guess next time you need a script written pop in here with the problem and we'll go through it. Or if you want to rewrite one of your current ones can help yo out with that.

The Diddler
Jun 22, 2006


adaz posted:

I guess next time you need a script written pop in here with the problem and we'll go through it. Or if you want to rewrite one of your current ones can help yo out with that.

It's funny you should mention that...I spent most of the day poking around at a script that I already have a version of, but this new one would be way nicer. Right now, it's a vbs that reads the username, sn, and lastlogin time from AD and writes it to an excel file. Then I run a macro to delete every row with a lastlogin time that's less than 30 days from today, so I'm left with a list of accounts that haven't logged into the domain in the last month.

I have the export part working, and I have the macro working, but I can't get the export to open and write in the file with the macro. It doesn't need to be so convoluted, but this is tons simpler than the first version.

LLJKSiLk
Jul 7, 2005

by Athanatos
I'm curious at ftp access using Powershell. I just downloaded it to play around with. Currently I'm using the cmd.exe ftp functions to download files, but my only error checking has been running FIND against the .log file I generate to make sure that there aren't any error codes.

Anyone have ftp examples using Powershell?

Lethargy
Sep 10, 2003

it's a sin ok
I was going to make this thread a while ago after I fell in love with PowerShell, but I just didn't have the time to really contribute more than an OP which would have been half as good as yours. Thanks!

edit: By the way, I highly recommend powershell.com's Power Tip of the Day. I get it in my email--if nothing else, it's neat little daily diversion that keeps you thinking about stuff you could do with pshell.

Lethargy fucked around with this message at 21:10 on May 11, 2010

adaz
Mar 7, 2009

Bob Cthulhu posted:

It's funny you should mention that...I spent most of the day poking around at a script that I already have a version of, but this new one would be way nicer. Right now, it's a vbs that reads the username, sn, and lastlogin time from AD and writes it to an excel file. Then I run a macro to delete every row with a lastlogin time that's less than 30 days from today, so I'm left with a list of accounts that haven't logged into the domain in the last month.

I have the export part working, and I have the macro working, but I can't get the export to open and write in the file with the macro. It doesn't need to be so convoluted, but this is tons simpler than the first version.

:science: Powershell has a better way!

First order of business, using the fancy new powershell v2 parameter statements for what we'll pass to the script - number of days to search back, and where you want the file exported:

code:
param(
[parameter(position=0,Mandatory=$true,HelpMessage ="Specify the number of days a user hasn't logged on that we should search for" )][string] $Days,
[parameter(position=1,Mandatory=$true,HelpMessage="filePath where the csv should be exported")][string] $FileLocation
)
We'll create a time object and use the passed $days variable. We have to use the built in ToFileTime method to convert it into the whacky format lastlogontimestamp uses - the number of 100 nanosecond increments since January 1st, 1600.

code:
$date = (Get-Date).AddDays(-$days).ToFileTime()
Now comes a LDAP search filter using System.DirectoryServices.DirectorySearcher (http://msdn.microsoft.com/en-us/library/system.directoryservices.directorysearcher.aspx). First, we need to bind to the root of the domain - replace Contoso.com with whatever your AD domain is. The [ADSI] is a type "Accelerator" that's used as a kind of shorthand way of accessing a class, method, function, etc. There are a couple built into powershell (http://www.nivot.org/2008/12/25/ListOfTypeAcceleratorsForPowerShellCTP3.aspx).

code:
$root = [ADSI]"LDAP://contoso.com"
Now, we create a new System.DirectoryServices.DirectorySearcher object & point it to your domain.

code:
$searcher = New-Object System.DirectoryServices.DirectorySearcher $root
We'll set a LDAP search syntax filter on it to find accounts that haven't been logged in at least 30 days. LDAP search syntax is The Devil, but hopefully you're already used to it from your vbscript coding days. If not, start having fun: http://msdn2.microsoft.com/en-us/library/aa746475.aspx

code:
$searcher.filter = "(&(objectCategory=user)(lastlogontimestamp>=$date))"
You only wanted a few properties returned, so let's set those. The [void] is a type accelerator that casts the return to, well, null. If you remove it you'll see the $Searcher object returns a number indicating how many items it's searching for everytime you add something. Doesn't hurt anything, but annoying if you leave it.

code:
[void]$searcher.PropertiesToLoad.Add("sn")
[void]$searcher.propertiesToLoad.Add("cn")
[void]$searcher.PropertiesToLoad.Add("lastlogontimestamp")
Now we execute the search and bind it to the $Users object

code:
$users = $searcher.FindAll()
Two options here. We can do some cleverness and create a comObject, bind to it, and do all sorts of excel stuff OR we can be lazy and just export that poo poo as a csv file. I'm feeling lazy, and CSV is an interesting case where the CMdlets supplied - Export-CSV, ConvertTO-Csv don't work as you expect them so you have to kind of have do it yourself most of the time.

We'll create a multidimensional empty array:

code:
$exportTable = @()
Start off with our standard For Loop - our starting index, when we stop, and how much to increment the counter each iteration.

code:
For($i=0;$i -lt $users.count;$i++) {
Now we'll create a custom object with the properties we care about.

code:
$CSVObj = "" | select SN,CN,LastLogonTimestamp
Add those properties to the object, we'll use the [string] accelerator to force the values to string instead of the propertyResultCollection type which won't convert nice.

code:
$csvObj.sn = [string]$users[$i].properties.sn
$csvObj.cn = [string]$users[$i].properties.cn
LastLogonTimestamp is in that moon format, so we'll have to convert it to something readable, we can use the built in FromFileTime method from the System.DateTime class like we did before.

code:
$csvObj.lastlogontimestamp = [System.DateTime]::FromFileTime([string]$users[$i].properties.lastlogontimestamp)
Now we add it to our array:

code:
$exporttable += $csvobj
Close out our For Loop with the standard } and pipe the results to Export-Csv

code:
}
$exporttable | Export-Csv $filelocation -noTypeInformation
To run the code, execute it from the powershell window like so:
code:
.\Test.ps1 -days 300 -filelocation c:\temp\blah.csv
The full script, coming in at a cool 22 lines :black101:

code:
param(
[parameter(position=0,Mandatory=$true,HelpMessage ="Specify the number of days a user hasn't logged on that we should search for" )][string] $Days,
[parameter(position=1,Mandatory=$true,HelpMessage="filePath where the csv should be exported")][string] $FileLocation
)
$date = (Get-Date).AddDays(-$days).ToFileTime()
$root = [ADSI]"LDAP://hhss.local"
$searcher = New-Object System.DirectoryServices.DirectorySearcher $root
[void]$searcher.PropertiesToLoad.Add("sn")
[void]$searcher.propertiesToLoad.Add("cn")
[void]$searcher.PropertiesToLoad.Add("lastlogontimestamp")
$searcher.filter = "(&(objectCategory=user)(lastlogontimestamp<=$date))"
$users = $searcher.FindAll()
$exporttable = @()
For($i=0;$i -lt $users.count;$i++) {
$CSVObj = "" | select SN,CN,LastLogonTimestamp
$csvObj.sn = [string]$users[$i].properties.sn
$csvObj.cn = [string]$users[$i].properties.cn
[string]$tmpDate  = $users[$i].properties.lastlogontimestamp
$csvObj.lastlogontimestamp = [System.DateTime]::FromFileTime([string]$users[$i].properties.lastlogontimestamp)
$exporttable += $csvobj
}
$exporttable | Export-Csv $fileLocation -noTypeInformation

adaz fucked around with this message at 05:14 on May 8, 2010

adaz
Mar 7, 2009

SiLk-2k5 posted:

I'm curious at ftp access using Powershell. I just downloaded it to play around with. Currently I'm using the cmd.exe ftp functions to download files, but my only error checking has been running FIND against the .log file I generate to make sure that there aren't any error codes.

Anyone have ftp examples using Powershell?

Well, this is one of those cases where powershell itself doesn't have a built in "ezmode" ftp client. If the plane jane FTP.exe isn't useful take a look at the System.Net.FTPWebRequest class:

http://msdn.microsoft.com/en-us/library/system.net.ftpwebrequest.aspx

A casual google search turns up this example script which is hopefully enough to get you started (maybe):

http://powershell.com/cs/media/p/804.aspx

If you have a specific task you'd like to do, let me know can write you out a sample script.

The Diddler
Jun 22, 2006


adaz posted:

The full script, coming in at a cool 22 lines :black101:

This was just what I needed, and with a few tweaks I can use it for a couple of other things, thanks!

adaz
Mar 7, 2009

Bob Cthulhu posted:

This was just what I needed, and with a few tweaks I can use it for a couple of other things, thanks!

Not a problem. Honestly, 75% of that script was stuff I have already written before in the course of doing my job. I find that a lot with powershell, once you write a few functions/scripts for your job you can reuse them endlessly and impress the higher ups or just speed up general development. I know, personally, I have quite a few built in to my powershell profile for easy mode things. For example, here is one I borrowed from somewhere on the internet that - if passed an email address - will return you the user object complete with already converted lastlogontimestamp,password last set, etc.

code:
############
# Get-User #
############
function Get-User($email)
{
        # this function should be passed the email of the user to be returned
        $root = [ADSI] "LDAP://your.domain"
        $searcher = New-Object System.DirectoryServices.DirectorySearcher $root
        $searcher.filter = "(&(objectCategory=person)(objectClass=user)(mail=$email))"
        $user = $searcher.FindOne()
        [System.Collections.Arraylist]$names = $user.Properties.PropertyNames
        [System.Collections.Arraylist]$props = $user.Properties.Values
        $script:userobj = New-Object System.Object
        for ($i = 0; $i -lt $names.Count){
             $script:userobj | Add-Member -type NoteProperty -Name $($names[$i]) -Value $($props[$i]) -force
             $i++
        }   
        if ($script:userobj.pwdlastset) {
            $script:userobj.pwdlastset = [System.DateTime]::FromFileTime($userobj.pwdlastset)
        }Else {
            $script:userobj | Add-Member -type NoteProperty -Name "pwdLastSet" -Value "Never" -force
        }
        if ($script:userobj.lastlogontimestamp) {
            $script:userobj.lastlogontimestamp = [System.DateTime]::FromFileTime($userobj.lastlogontimestamp)
        } else {
            $script:userobj | Add-Member -type NoteProperty -Name "lastLogonTimeStamp" -Value "Never" -force
        }
}
So the nice thing is since I put in my powershell profile and somebody asks me "hey when did user X last logon" it's a simple command get-user user@domain.com. Or, I can just reuse that function in my scripts if I need to or build a UI around it.

Or even just emailing a user (powershell v1 compliant, so no fancy param statements)
code:
##############
# Email-User #
##############
Function Email-user
{
    param([string]$recipient=$(throw 'recipient is required'), [string]$body=$(throw 'message body is required.'),`
    $from = $(throw 'from required'),[string]$Subject=$(Throw 'subject is required'))
    $Email = new-Object System.Net.Mail.MailMessage
    $client = new-Object System.Net.Mail.SmtpClient "your.outgoing.mail.server"
    $client.UsedefaultCredentials = $true
    $Email.To.Add($recipient)
    $Email.From = $from
    $Email.Subject = $Subject
    $Email.Body = $Body
    $Client.Send($email)

} 

adaz fucked around with this message at 07:02 on May 11, 2010

The Diddler
Jun 22, 2006


adaz posted:

You only wanted a few properties returned, so let's set those. The [void] is a type accelerator that casts the return to, well, null. If you remove it you'll see the $Searcher object returns a number indicating how many items it's searching for everytime you add something. Doesn't hurt anything, but annoying if you leave it.

code:
[void]$searcher.PropertiesToLoad.Add("sn")
[void]$searcher.propertiesToLoad.Add("cn")
[void]$searcher.PropertiesToLoad.Add("lastlogontimestamp")
Now we'll create a custom object with the properties we care about.

code:
$CSVObj = "" | select SN,CN,LastLogonTimestamp
Add those properties to the object, we'll use the [string] accelerator to force the values to string instead of the propertyResultCollection type which won't convert nice.

code:
$csvObj.sn = [string]$users[$i].properties.sn
$csvObj.cn = [string]$users[$i].properties.cn

I ran into some issues with this today. I tried to add some additional properties to look for, but they don't print. I added them to the 3 spots I quoted, and instead of displaying like the rest, they create a column where I tell it and then leaves it empty. For example, if I wanted to add SamAccountName I would do this:

code:
[void]$searcher.PropertiesToLoad.Add("SamAccountName")
code:
$CSVObj = "" | select SN,CN,LastLogonTimestamp,SamAccountName
code:
$csvObj.SamAccountName = [string]$users[$i].properties.SamAccountName
Did I miss something? I assume that the LDAP properties remain the same with Powershell, but I couldn't find anything that specifically said that, they just implied it. I tried it with a handful of other properties to see if I could track down my error, and they all did the same thing.

adaz
Mar 7, 2009

Bob Cthulhu posted:

Did I miss something? I assume that the LDAP properties remain the same with Powershell, but I couldn't find anything that specifically said that, they just implied it. I tried it with a handful of other properties to see if I could track down my error, and they all did the same thing.

LDAP properties & names are the same with powershell as vbscript.

What you ran into was something really stupid and annoying with the language: Case Doesn't Matter Except When It Does. It's my fault I'm used to it so didn't even think to mention it - the properties returned off of the search result collection are lower case ONLY. If you try to access them in uppercase it'll return null and not throw and error or anything helpful. Your code will work fine with this one change:

quote:

$csvObj.SamAccountName = [string]$users[$i].properties.samaccountname


I actually filed a bug report about it as powershell is essentially supposed to be case agnostic but apparently it's just How .NET Works

e: Note that the $csvobject is also retarded in that you can assign using any case - it'll even display in that case - but gently caress all if you can access it with specific case:



adaz fucked around with this message at 05:19 on May 12, 2010

The Diddler
Jun 22, 2006


adaz posted:

What you ran into was something really stupid and annoying with the language: Case Doesn't Matter Except When It Does.

Hey, it worked! That seems pretty stupid, though. Is there an online resource for that sort of thing? I've tried Technet's scripting center, but it's nearly impossible for me to find what I'm looking for there.

adaz
Mar 7, 2009

Bob Cthulhu posted:

Hey, it worked! That seems pretty stupid, though. Is there an online resource for that sort of thing? I've tried Technet's scripting center, but it's nearly impossible for me to find what I'm looking for there.

I agree it's stupid considering the entire scripting language seems to be case-agnostic. The only time I have ever seen it "care" about case is accessing the properties on an object. Other than that I have never seen it matter which is why I just instinctively make it lower case.

I sent a email to the scripting guys asking for clarification, we'll see.

adaz fucked around with this message at 06:13 on May 13, 2010

Dessert Rose
May 17, 2004

awoken in control of a lucid deep dream...

adaz posted:

LDAP properties & names are the same with powershell as vbscript.

What you ran into was something really stupid and annoying with the language: Case Doesn't Matter Except When It Does. It's my fault I'm used to it so didn't even think to mention it - the properties returned off of the search result collection are lower case ONLY. If you try to access them in uppercase it'll return null and not throw and error or anything helpful. Your code will work fine with this one change:


I actually filed a bug report about it as powershell is essentially supposed to be case agnostic but apparently it's just How .NET Works

e: Note that the $csvobject is also retarded in that you can assign using any case - it'll even display in that case - but gently caress all if you can access it with specific case:



I don't know if it actually works if you spell it right, but I'm sure it won't work if you spell it wrong.

adaz
Mar 7, 2009

Ryouga Inverse posted:

I don't know if it actually works if you spell it right, but I'm sure it won't work if you spell it wrong.

whelp, I'm dumb. Quite right if you actually spell the property name right off of a object it works fine. Still doesn't work off a search result collection though.

The Diddler
Jun 22, 2006


I've been messing around with Powershell for a week or so, and I've found another question. Is there something you can do at the command prompt that you can't do with Powershell? I do a pretty fair amount of my daily work in a command window, and I've tried a half-dozen or so commands that worked in Powershell. It would be nice to not have to keep switching shells to do different things. I tried to google this, but the only real result was about how awesome Powershell is for scripting.

Victor
Jun 18, 2004
You should know about Powershell Pipeline Performance. Other than that, Powershell's string escaping mechanism is not weird, and echo is no longer special.

adaz
Mar 7, 2009

Bob Cthulhu posted:

I've been messing around with Powershell for a week or so, and I've found another question. Is there something you can do at the command prompt that you can't do with Powershell? I do a pretty fair amount of my daily work in a command window, and I've tried a half-dozen or so commands that worked in Powershell. It would be nice to not have to keep switching shells to do different things. I tried to google this, but the only real result was about how awesome Powershell is for scripting.

I can't think of anything that won't work in powershell as opposed to cmd as far as built in windows commands & utilities. Some of the common commands are different as far as what is being used - for instance dir is actually an alias for Get-ChildItem, but they do the same thing.

supster
Sep 26, 2003

I'M TOO FUCKING STUPID
TO READ A SIMPLE GRAPH
Can you do the following in Powershell?

1) Open an application if not already open (check process name? window name?).

2) Bring the opened (or preexisting) application to the top.

3) Move and resize the opened (or preexisting) application.

Victor
Jun 18, 2004
Yes you can; you can use the System.Diagnostics.Proccess class, and/or P/Invoke. Do you need the Win32 APIs for doing what you want to do? (I would first check out the Process class to see how much you can do with that.)

adaz
Mar 7, 2009

If you wanted to easy mode it, someone wrote a powershell snapin that will do what you want I think:

http://huddledmasses.org/control-windows-from-powershell/

Z-Bo
Jul 2, 2005
more like z-butt
Been hacking in Powershell lately to automate some bullshit garbage that I can now pass off to somebody who can just click the script and monitor it to make sure nothing failed.

See http://z-bo.tumblr.com/post/657962194/optimizing-vba where I have been dabbling in using Powershell to mess with CSV files, using the Excel COM API. It was rough getting used to the API and what idioms worked best, but now that I have done a bit I like it. A cheat sheet for Powershell and Excel would be a nice addition to the Powershell community.

I'll bump this thread in the future with some neat Powershell idioms Victor and I came up with about a year and a half ago that have proven rock solid and seriously cut down on lines of code.

adaz
Mar 7, 2009

Z-Bo posted:

Been hacking in Powershell lately to automate some bullshit garbage that I can now pass off to somebody who can just click the script and monitor it to make sure nothing failed.

See http://z-bo.tumblr.com/post/657962194/optimizing-vba where I have been dabbling in using Powershell to mess with CSV files, using the Excel COM API. It was rough getting used to the API and what idioms worked best, but now that I have done a bit I like it. A cheat sheet for Powershell and Excel would be a nice addition to the Powershell community.

I'll bump this thread in the future with some neat Powershell idioms Victor and I came up with about a year and a half ago that have proven rock solid and seriously cut down on lines of code.

Working with CSV in powershell is way more trickier than it should be, I have used the excel com api before but mostly found it easier to make a custom object and add it to a multidimensional array then screw with the API.Whatever works though you know.

I had a training class with an MS rep and, for what it's worth, the powershell team is aware of how awful the export-csv cmdlet is and has been working on improving it for both v2 and v3, so hopefully at some point I can just pipe a random object to export-csv and have it actually export correctly instead of all the crazy jijtsu we have to do now.

It's kind of ridiculous you can't just bind to an AD object and | it to export-csv. It just Won't Work, and that's a major problem, explaining multidimensional arrays and custom objects is a lot harder than just telling someone to pipe their results to a a cmdlet (And have -append actually work).

In the meantime I've made a couple scripts that might come in handy for Goons. The latest one finds a couple files on people's homedirs and replaces text. Pretty nifty. How you retrieve the ad Object is up to you, you can bind to an OU an iterate through all the members as an example but here is how a simple -replace works.


code:
  # Bind to AD Object, retrieve home directory.
    $UserObj = [adsi]"LDAP://cn=cn,ou=blah,ou=blahmore,dc=blah,dc=blah"
    if($userObj.Homedirectory) {
        # Make sure homedir exists.
        if($(test-path $homedir) -match $true) {
            $Homedir = $UserObj.Homedirectory.ToString()
            # Modify file it exists.
            if($(test-path "$homedir\config\notes\notescfg.txt") -match $true){
               # Check to see if string exists, if so replace.
               [boolean]$stringExists = select-string -pattern "a string pattern here" -path `
               $homedir\some\directory\and\path.txt -quiet
               if($stringExists -match $true) {
                   write-host "replacing for $($userobj.displayname)"
                  $Cfg = Get-Content $homedir\some\directory\and\path.txt
                  $cfg = $cfg -replace "string1","string2"
                  $cfg | Set-Content $homedir\some\directory\and\path.txtt
            }
      }
}  
Just the kind of cool stuff you can do with powershell. This went through ~7000 homedirs and fixed some settings relating to a notes --> exchange conversion (politics) and saved countless calls and hours of helpdesk time. Just the type of "simple" task powershell was made for.

adaz fucked around with this message at 16:42 on Jun 4, 2010

Adbot
ADBOT LOVES YOU

Walked
Apr 14, 2003

Anyone know how to query an Exchange 2007 server and check the number of messages in specific queues?

Working on automating my staff's on-call checks for them; finding iffy documentation on Exchange queries. (To be fair; half the internet is blocked at this office too)

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