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

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.

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

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

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

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 :)

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

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.

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.

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

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

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

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.

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.

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/

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

adaz
Mar 7, 2009

Z-Bo posted:

How do I use set-acl to get rid of mobsync.exe's TrustedInstaller ownership and revoke traverse/execute from Everyone?

Easy solution found it while searching on how to take owernship of a file in powershell. Create something called test.txt and set whatever permissions you want on it (that will be mirror'd on mobsync.exe) then do this:

code:
$objUser = New-Object System.Security.Principal.NTAccount("fabrikam", "kenmyer")
$objFile = Get-Acl C:\Scripts\Test.txt
$objFile.SetOwner($objUser)
Set-Acl -aclobject $objFile -path C:\Scripts\Test.txt
Here is the scripting guys article on it:

http://blogs.technet.com/b/heyscriptingguy/archive/2008/04/15/how-can-i-use-windows-powershell-to-determine-the-owner-of-a-file.aspx

Bob Cthulhu posted:


Another question: any resources on how to format the output of select-string? I have a 2-liner that produces pretty decent output if I paste it into my session, but if I run the .ps1, the output changes.

Can you give us the example? I mean, select-string outputs whatever it finds as a string, not sure what you want to do with it.

adaz fucked around with this message at 18:37 on Jun 21, 2010

adaz
Mar 7, 2009

Bob Cthulhu posted:

I'll have to look when I get to work. I just tried it at home and it worked like I wanted it to.

Powershell v1 versus v2 maybe? Also keep in mind that if powershell is returning some really stupid formatting or type for whatever reason the [string] type accelerator can be golden. Works fantastic for a lot of things, especially ADSI type searches that output fine until you try to work with them.

Still, select-string should always return a string as far as I'm aware.

adaz
Mar 7, 2009

Bob Cthulhu posted:

I'm running Win7 on both machines. That comes with v2 out of the box, right?

The differences were something like:


The first one is what I want, and the second one is the one that just shows up.

What is the full code though that's generating all this? I'm not sure.. what are you piping to select string (or inputting)?

Spudman posted:

Just posting here to share my Powershell success story, and to post something I wrote that might help other people. It's a script that scans an entire range of IP addresses, and if it is able to ping the IP, it attempts a couple of WMI queries to get the name and model of the machine. It also includes the option to scan the machine further and retrieve errors that it has in its logs from the last 24 hours. Then it writes all that stuff to (a) file(s). You need to provide sufficient domain credentials, so obviously you have to run it again for the other domain if you have more than one domain in the same IP range, like I do. I suppress the WMI access denied errors because I get a lot of them because I have an untrusted domain coexisting in the same IP range as my main domain. Warning: It sets the global error action preference to silent so you need to set it back to normal after you're done or write it into the script or else you won't see errors in Powershell until you turn it back to normal. Please tell me if you see anything that I could have done more efficiently, or better, or have an idea or suggestion that would make it even more awesome. Thanks.



That's a cool script and I like the use of Jobs which I've hardly touched but really should, especially when dealing with the usually incredibly slow WMI. I would suggest checking out the Get-EventLog cmdlets instead of using WMI to retrieve the event log, they're usually much faster and have good granular control over what it's outputting/how it's outputting.

adaz fucked around with this message at 22:59 on Jun 22, 2010

adaz
Mar 7, 2009

Bob Cthulhu posted:

This is the script:
code:
Get-ChildItem C:\users\user\desktop\pim
grep -pattern "EXCEPTIONS" -path "C:\users\user\desktop\pim\*.*"
Grep is defined in my profile as an alias for select-string. I tried it using select-string instead of grep, and it did the same thing. When I paste it into powershell, it does this:


This is generally going to be run against a directory of 7 to 30 files, which is going to make it really hard to read. Any ideas?

That is really bizarre and I don't know why it is doing that to you. I tried quite a few things to get your original code to work executed as a ps1 but nothing would, sorry. I did, however, find a way around that output problem and it that works just fine.


code:
$dir = Get-Childitem C:\users\user\desktop\pim
$dir  
$dir | select-string "EXCEPTIONS"

adaz
Mar 7, 2009

Bumping this thread in case there are any more questions and the scripting guys have been running a great series on powershell & active directory this week that everyone should check out:

http://www.games-workshop.com/gws/catalog/productDetail.jsp?catId=cat440342a&prodId=prod840005a

(although I still think the [adsisearcher] accelerator is retarded)

adaz
Mar 7, 2009

I don't have an exchange environment handy to test this on, but my experience with powershell formatting and type conversions wackiness leads me to believe something like this would work to fix it (although I'm sure someone with more knowledge would know a better way).

code:
[array]$arrStores = Get-ExchangeServer | where {$_.IsMailboxServer -eq "$True" }  | Get-MailboxDatabase -status

$arrstores |ft servername,storagegroup,mounted;
You force it convert the data to an array instead of relying on the built in type filters, that usually works for silly problems like this

adaz
Mar 7, 2009

Walked posted:

Unfortunately same error; got me excited too :(

well, if Victor is correct you can try putting in a

code:
| select-Object servername,storagegroup,mounted | ft
and see if that works

adaz
Mar 7, 2009

Walked posted:

No good on that one adaz; but feels close. Got a few things I'm going to try; this is annoying as anything though.

A random google search says this - what happens if you remove the ; (actually I don't even know why you included it, it's strictly speaking not necessary

quote:

It's because the ; character doesn't terminate the pipeline, but does terminate the expression. The first expression has filled the success pipeline with objects that PowerShell "wants" to format as a table... then you're calling a second expression. The ; character isn't meant to be used in a pipeline like that - it's primarily for use within a script or subexpression where the success pipeline isn't becoming involved. Keep in mind that the pipeline ends in Out-Default, which doesn't deal with unformatted data; it's made an invisible call to a Format cmdlet, which generates formatting objects.

adaz
Mar 7, 2009

^^^
I still don't get that, it really doesn't make any sense =/. But then again the format returned/accepted by some cmd-lets is completely retarded, and what they will accept can boggle the mind (see Export-Csv for a perfect example of an awfully designed cmdlet as far as what input it'll accept).

Orbis Tertius posted:

I'm working on an Intranet-only site (running Apache/PHP/MySQL) and I recently implemented some automation stuff involving Word 2003. I did this by having the shell_exec PHP function run a console program I wrote in C#, which handles all the automated .doc parsing stuff I needed to do. This project was a non-starter for awhile; I couldn't seem to get anything to work, but I stumbled across an archived Microsoft article that walked me through configuring the user for Word to be Interactive User (in Word's DCOM Config properties), and lo a behold that did the trick. However, as far as I comprehend windows security, this is a very poor solution. This project was a crash course on a number of things, windows security being one of them, so I'm kind of flailing around here. System admin stuff is not my area of expertise.

I'd like to return Word to its default User settings, and work out some other way of executing my console program that still keeps Word from crapping out behind the scenes. Can Powershell help? I ran across this Hey Scripting Guy article that looks like it could be adapted to my purposes. Here's what I was thinking: In my php code, I'd have the shell_exec function start powershell with the path to a script (Working from the syntax used here, to start with), and for the script I'd modify the Hey Scripting Guy example to have it just run the console program instead of doing the scheduling stuff with the .vbs script.

I'd appreciate any help figuring out how to make the Powershell script, but mainly I'm wondering if this is a) feasible, and b) a more secure solution than what I've currently got going.

So, from your example, you want your php webpage to invoke a remote powershell script that will read/do some stuff with word values and then.. return a result? or just ~do some things~ with word? What exactly are you doing with word? Powershell can do tons with word, and quite easily, but it really depends on what exactly you want done for anyone to say if powershell is your best bet or not.

adaz fucked around with this message at 18:54 on Oct 15, 2010

adaz
Mar 7, 2009

Walked posted:

This is a simple one.

Reading the Windows Powershell Cookbook (and gently caress me this book rules).

A lot of their scipts call on others that were written.
Generally in all my readings its always "and you can use your own PS scripts as if they were cmdlets, call them like: PS> yourscripthere.ps1 - see isnt that awesome?!"

But the cookbook has you calling them sans extension. Is there a default search location for your own cmdlets or do I just need to use an alias or what?

Building up my script library at work to make life easier here. Want to streamline it best possible.

It's called Dot Sourcing and got a bit easier in V2. The scripting guys have an article up on it a few weeks old that probably do a better job explaining it than I would:

http://blogs.technet.com/b/heyscriptingguy/archive/2010/10/07/reuse-powershell-code-to-simplify-script-creation.aspx

What I usually end up doing is tossing every function I think might be reused into like common.ps1 and all my scripts just dot source that on load. You can also just load them into your powershell profile if you mainly run the scripts as yourself, then you really don't have to worry about dot-sourcing - they are in your profile and can be run natively without any of the ps1 stuff.

adaz
Mar 7, 2009

Walked posted:

gently caress. Thank you.

:)

edit: Nopee; that had a complete lack of an append option. Meh.
Start-Transaction / Stop-Transaction covered my exact goals, which was just to pull all the results of a script run. It fucks up the carriage returns in Notepad; but thats minor really. It mostly looks aaaaa-ok.

No worry though :)

Write-Host should also work as it doesn't consume the pipeline

adaz
Mar 7, 2009

Walked posted:

Is this possible?

I've got a script that requires our domain admin account to run. So right-click, run as adminsitrator, good to go.

Then I've got it launching an outlook window and pre-populating the mail message with the results of the report.

Problem is, I cant get them to work together. If I do them together, either the administrative checks dont run (needs admin priveleges) or the outlook application wont launch (no outlook profile for the domain admin user).

Is there a way to tell it to execute a code block as the currently logged in user on a machine? I cant find much.

Outlook is such a bitch to work with and I've run into similar problems like you're describing. The issue is the profile problems, and I'll try to not rage too much here but this is so annoying to work around. There is a way in the ComObject to specify a different username/password but I've never been able to get it to work (probably because we're running on a different domain in a different forest than our Exchange domain). You might want to check into that.

So your options are to

1.) use system.net.mail to send your email through a friendly smtp server bypassing outlook altogether

2.) Check in the outlook object for how to log on as a different user (http://msdn.microsoft.com/en-us/library/bb208225%28v=office.12%29.aspx)

3.) use the old runas.exe

code:
& 'C:\windows\system32\runas.exe' /user:mydomain\myusername "C:\Program Files\Microsoft Office\Office12\Outlook.Exe"
4.) use exchange web services which I have very little experience with other than briefly playing around for a few hours one bored day.

adaz fucked around with this message at 22:09 on Oct 22, 2010

adaz
Mar 7, 2009

Walked posted:

SMTP isnt an option; user needs a chance to review the message before it fires off.

gently caress it. Not that big a deal.
Question: Would a start-process from Powershell 2.0 work, if I kicked off a secondary script to do the outlook tasks? Such a bad work around though.

Well, something like this should work if that's the case (haven't tested it, replace outlookprofile with name of profile you want to login to)-

code:
$cred = Get-Credential
start-process outlook -credentials $cred
$OutlookApp = New-Object -COM Outlook.Application 
$OutlookMapiNamespace = $OutlookApp.GetNamespace("mapi")
$OutlookMapiNamespace.Logon("OutlookProfile")

adaz
Mar 7, 2009

Walked posted:

wmi stuff

well $m.psbase.Get() will refresh those values if that is what you're looking for? OTherwise you can check out the system.diagnostics namespace, might have more of what you're looking for.

adaz
Mar 7, 2009

bob arctor posted:

I'm trying to create a script which when invoked will recursively delete all files older than 5 days from a folder. Then recursively copy all files < 3 days old into that folder. Unfortunately while I can list an appropriate set of files with:

code:
$DateToCompare = (Get-date).AddDays(-3)
Get-Childitem –recurse | where-object {$_.lastwritetime –gt $DateToCompare}
I can't seem to get it to copy the same set of files I've tried a few variations on the following. It doesn't look like I'm properly referencing the objects for the items, but I'm not quite sure of the syntax to do what I want with the files.

code:
$DateToCompare = (Get-date).AddDays(-3)
copy-item c:\users\xxx\source c:\users\xxx\powershelltest -recurse | where-object {$_.creationtime –gt $DateToCompare} 

E: first solution wouldn't work, only copied directories. THIS will work though.

code:
$DateToCompare = (Get-date).AddDays(-3)
get-childitem -path c:\users\xxx\source -recurse | copy-item -destination c:\users\xxx\powershelltest -filter {$_.creationtime -gt $datetocompare}

adaz fucked around with this message at 06:06 on Oct 30, 2010

adaz
Mar 7, 2009

Victor posted:

This should do ya:
code:
Get-ChildItem                              | 
    ? { $_ -is [System.IO.DirectoryInfo] } | 
    % { Get-ChildItem $_ }                 | 
    ? { $_ -is [System.IO.DirectoryInfo] }
(To those better at PowerShell, I'd love to know if there are better ways to do the above.)

e2: something like this but it's not easier than yours and I can't get the drat thing to work right anyways so you win. I can't really think of an easier way to do it than how you did to be honest.

quote:

get-childitem "C:\temp" | where-object {$_.psiscontainer -eq $true} | select-object $_.fullname | get-childitem | where-object {$_.psiscontainer -eq $true}

adaz fucked around with this message at 17:29 on Nov 1, 2010

adaz
Mar 7, 2009

I don't mind quest's AD cmd-lets but I find it easier to just write my own, also in a large environment you can't necessarily be sure those cmdlets will be installed on every computer. They definitely save time though if you're only worried about working from your own pc, or that your scripts will always have access to them.

As far as your problem, I haven't ever really touched offline files and I'm not running windows 7 at the moment so I can test anything. However, does this scripting guys article on working with them help you at all as far as breadcrumbs? Hopefully someone else has worked with them before can help you.

http://blogs.technet.com/b/heyscriptingguy/archive/2009/06/02/how-can-i-work-with-the-offline-files-feature-in-windows.aspx

adaz fucked around with this message at 22:19 on Nov 10, 2010

adaz
Mar 7, 2009

Z-Bo posted:

I have an idea and want to throw it out there to get feedback from goons.

What do you all think about the idea of writing a PowerShell snap-in that can automatically report on stuff like licenses and installed software on a machine?

It seems with WMI we can pretty much automate what a free solution like LANSweeper does, without requiring a client-side executable for it to work. PowerShell would simply use the new remoting features in 2.0 and you'd automatically get ops reports, centralized into perhaps a SQL Server database so we could use SQL Server Reporting Services for building dashboards like LANSweeper has.

That would most definitely work and be free, not sure how fast it would be but it's something that would probably only need to run once a day.

adaz
Mar 7, 2009

Cronus is quite right that's the best method of doing it. To load the values from your CSV file, assuming you have the computer common names in a column called computers, use something like this:

code:
$computers = import-CSV C:\temp\locationofcsv.csv
foreach($computer in $computerS) {
 # do what Cronus said
$data = gwmi -class)
}
When iterating through the CSV with ForEach remember you accessing each "row" in the CSV, then use the column header to access the data contained in each column in that row. You could add some fancy stuff to ping them first if they might be down, up to you!

adaz
Mar 7, 2009

You'd do something like this

code:
# This is my code.ps1
param([parameter(position=0,Mandatory=$true,HelpMessage ="Need a Source Directory" )]$sourceDir,
[parameter(position=1,Mandatory=$true,HelpMessage="Need a Destination Directory")]$DestDir) 

function Fun {
	param([string]$srcdir,$dstdir)
        write-host "srcdir: $srcdir dstdir: $dstdir"
}

Fun -srcdir $sourceDir -dstdir $destDir

# end of file

.\blah.ps1 -sourceDir "c:\blah" -DestDir "D:\blah.txt"
The param names are what are called with dashes, and tab completion works fine on them as well as long as you explicitly declare them in param. You can then do the same thing inside a script to call a function, using -paramname



adaz fucked around with this message at 20:36 on Apr 8, 2011

adaz
Mar 7, 2009

That's a pretty nifty little way to use powershell. Really shows the flexibility and how "easy" it is to build scripts with it, even scripts that might hook into another application.


One minor thing is Echo is actually an Alias for Write-Output. I only say this because, technically, you should use Write-Host for outputting things to the console as Write-Output puts things in the pipeline and can lead to weird things happening if you're accepting pipeline input. Also, write-host has built in options for color formatting and all that.

Also a random thing but this:

code:
$fall = $dstdir + "\" + $fname + ".m4v"
in powershell can also be written as below, some people like it your way, some people write it the other way - it's more personal preference than anything. Enclosing everything in "" makes it a bit more easier to read I think in code, but some people are so used to escape characters they hate it.

code:
$fall = "$dstdir\$fname.m4v"

adaz fucked around with this message at 22:47 on Apr 8, 2011

Adbot
ADBOT LOVES YOU

adaz
Mar 7, 2009

It's probably nothing to worry about, just a more general FYI.

The string expanded/quoting thing is pretty nifty but one thing that will trip people up is sometimes it'll blow up if you're trying to access the subproperties of an object.

Like say I have a directory entry (a .NET way of accessing a entry in Active Directory) and want to put the postal code in a string. Look at this fun behavior:


So, what you want to do is enclose that in $() (a sub-expression) to cause powershell to explode it first before it takes the rest of the string in:

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