Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I've recently written a couple thousand lines of PowerShell.

It's a miserable language, full of unexpected behaviors and badly designed features. Oh, it's got some interesting stuff going on, but things like:

- including the text of 'echo' (and stdout generated by invoked tools) in a function's result was a big surprise. You wind up piping stuff to "Out-Null" a lot in defense of this, since a lot of Windows tools are stupidly chatty ("The Blffgh command succeeded with status 0!") and that's awkward.

- so was the "unwrapping" that happens when a function returns a list (return [a,b] you get the list you want, try to return [a] and it rips off the list and just returns the inner 'a', and I think an empty list results in null . . . just wow).

The sole reason I use PowerShell is because it's good for mucking with OS-level objects in Windows, stuff that I would otherwise have to write native code to frob. And sometimes that is actually kind of attractive.



I write hundreds of lines of PowerShell every day, and quite like it as a language. Coming from a C# developer background, I initially struggled when I first started with PowerShell, but I believe this was mainly 1: the many quirks and issues with the early versions of PowerShell (this was back in PowerShell v1 days), and 2: that I was trying to use PowerShell as if it were C# (or a similarly 'structured' language).

Yes, the output from calling native commands within functions can be a little tricky, and something that annoyed me initially, but once you understand the concept of passing objects around, not strings, you quickly get used to either capturing the output into a variable ($BlahResult = .\blah.exe /foo bar /baz 2>&1), where you can then access the string output (stdout) and any ErrorRecord (stderr) lines, or as you suggested, piping to Out-Null if you don't actually need the output. If you're writing your own 'echo' type statements for debugging or information purposes, you learn to use Write-Verbose or Write-Debug instead of Write-Output (which is what happens by default).

The unwrapping example was something that got me initially, because I was used to defining a collection (array, List, whatever), populating it, and then returning that collection... and then having the calling code iterate over that collection. "The PowerShell way" is to not focus on the collection/array at all, but instead just let it take care of that and deal with the items themselves. If you pipe the output of a function to Foreach-Object, it will work correctly - if it's a single object returned, that's what will be $_ in the loop, if it's multiple, they will all be passed in one-by-one as $_... if you actually want to access the array itself (for example to count the number of items), then you can enforce the array type by wrapping it in an additional array (i.e. @($Users).Count).


If you stay in PowerShell I guess that's okay. But I interop with Python and other things a fair amount, via JSON, and have had to write wrappers and guards against the unwrapping nonsense.

But "... quickly get used to" is more readily translated as "Spent an afternoon debugging, found the reason, did a forehead plant on the keyboard, then tried to remember on every invocation, so as not to get burned again."

Python, LISP, Smalltalk and a host of other languages made much better decisions in similar areas.


Powershell can convert JSON objects to .NET objects with ConvertFrom-Json. And it outputs decent JSON by piping objects to ConvertTo-Json. I think few people know about these cmdlets, but combined with Invoke-WebRequest, they offer access to any JSON API.

Since .NET objects can't be copied from one session and pasted through RDP into another, I'll often take an array or other object, and pipe it through ConvertTo-Json into clip.exe, which throws the JSON onto my clipboard. In the RDP session, I'll pipe Get-Clipboard into ConvertFrom-Json, all written into a variable.

Piping ( | ) sends objects from one cmdlet to another. Many cmdlets and functions have a preset parameter for pipeline input, and it's easy to specify this when making your own functions. Foreach-Object ( % ) can have cmdlets act against each item in the pipeline, with $_ being the "this" character. Just remember to wrap them in curly brackets, as this marks them as as the script block being invoked.

Additionally, Powershell can interoperate with CSV just as easily, with ConvertFrom-CSV and ConvertTo-CSV.

Want a simple API? Here's a one-liner - just host the output with IIS:

While ($true) {Get-Process | ConvertTo-Json > C:\www\ApiFile.html; sleep 60}

For a more interactive API, the .NET HTTPListener class can easily be called and built upon.

My favorite part of the language is the type system, where you just put the .NET type in square brackets before the variable. [string]$myString = "Hello World"


Here's another absolutely classis interaction with PowerShell. Let's make a list of strings; I'm partly interested in what CSV conversion does to values that already have commas.

    PS> $x = @('a', 'b', 'c', 'd,xx', 'e')

    PS> $x
    a
    b
    c
    d,xx
    e
Okay, that's great. Now:

    PS> $x | ConvertTo-json
    [
        "a",
        "b",
        "c",
        "d,xx",
        "e"
    ]
Hey, conversion to JSON works! But let's try the CSV conversion:

    PS > $x | ConvertTo-Csv
    #TYPE System.String
    "Length"
    "1"
    "1"
    "1"
    "4"
    "1"
... and I think I just died a little inside. I didn't even get to answer the original question I had about escaping separators. I have no earthly idea why PowerShell decided to spew the lengths of the elements rather than the values, or what the hell the other gorp in the output is.

The official documentation is not much help. I could go do some research on StackOverflow and so forth, but . . . I've lost the will to live. For this little exercise I've reached the point of uncaring.

This is basically my everyday interaction with PowerShell; try out something new, have it fail in a way that this LISP / Python / C++ / etc. veteran would never expect, and spend 20 minutes or maybe a whole bloody day researching why, or working around the behavior with other tools. I could become an expert in PowerShell, but I'm not interested, I just want things to work in reasonable ways. I've got shit to do. [I'm writing this HN post because in ten minutes I get to dive into some more PowerShell]

PowerShell has repeatedly failed the "reasonable expectation" test. I can't depend on it to be a lightweight and useful tool. Instead, every time I use it I can expect to spend 60 percent of my time working around capricious nonsense, dredging through postings by other people who have thankfully preceded me.


It's about knowing what the function expects to receive.

What you got seems unexpected initially, but as you read the doc on this function (which took me a good 20 secs) you may think it is indeed well conceived, and realize that it was weird to expect passing an array of strings to it and expect it to work.. I don't even know what it should've done.

Should it have given you a single line of comma separated values? But then what should you give to the function to have multiple lines of csv? An array of array of strings?


"$arrayOfObjects | ConvertTo-Csv" will create one row per object in the array, with a column for each public field of the object. That's exactly what happened, and it seems like a reasonable behavior to me.

Do you have an alternative? Maybe for the edge case where the contents of the CSV is a list of primitives, it should give you a "Value" column? Or did you expect that, given an array of stuff, the best approach is to encode the whole array in a single CSV row?


> it's good for mucking with OS-level objects in Windows, stuff that I would otherwise have to write native code to frob.

In about one-and-a-half months, earlier this year, I gave my Lisp dialect an FFI, using which I was able to completely translate the MSDN "Your first Windows Program" C example, almost line by line:

http://nongnu.org/txr/rosetta-solutions-main.html#Window%20c...

After a bunch of FFI definitions of constants, structures and functions, the core of code is just:

  (defun WindowProc (hwnd uMsg wParam lParam)
    (caseql* uMsg
      (WM_DESTROY
        (PostQuitMessage 0)
        0)
      (WM_PAINT
        (let* ((ps (new PAINTSTRUCT))
               (hdc (BeginPaint hwnd ps)))
          (FillRect hdc ps.rcPaint (cptr-int (succ COLOR_WINDOW) 'HBRUSH))
          (EndPaint hwnd ps)
          0))
      (t (DefWindowProc hwnd uMsg wParam lParam))))
 
  (let* ((hInstance (GetModuleHandle nil))
         (wc (new WNDCLASS
                  lpfnWndProc [wndproc-fn WindowProc]
                  hInstance hInstance
                  lpszClassName "Sample Window Class")))
    (RegisterClass wc)
    (let ((hwnd (CreateWindowEx 0 wc.lpszClassName "Learn to Program Windows"
                                WS_OVERLAPPEDWINDOW
                                CW_USEDEFAULT CW_USEDEFAULT
                                CW_USEDEFAULT CW_USEDEFAULT
                                NULL NULL hInstance NULL)))
      (unless (equal hwnd NULL)
        (ShowWindow hwnd SW_SHOWDEFAULT)

        (let ((msg (new MSG)))
          (while (GetMessage msg NULL 0 0)
            (TranslateMessage msg)
            (DispatchMessage msg))))))


True, but then you have to develop in Lisp :p

I'm kidding, I'm kidding. But on the other hand, related to your example: the Windows APIs are in my opinion awful. Look at that CreateWindowEx call...


> True, but then you have to develop in Lisp

Sorry to poop your meme frolicking here, but your statement is flatly untrue at face value; other non-native languages exist that provide access to Windows API's.

Obviously, since I made this for myself at considerable effort in my free time over more than eight years, developing in Lisp must obviously be something I really want, not something I have to do.


Non-native languages with Win32, access: VB 6, VBScript/ JScript (CScript), everything on top of .Net, Python, Perl, heck, even Autohotkey.

Regarding the second part, don't take it so personally, I was just joking. You're also on the internet: if a lighthearted comment such as mine annoys you, a real internet troll will ruin your day...


I agree on those 2 points.

regarding your second point, I don't like this unrolling either.

The trick seems to be instead of :

    return $mylist
Add a comma in front:

    return ,$mylist
https://stackoverflow.com/a/16122464

This will make sure [a] never gets changed to a, and [] never gets changed to $null


There is a slight better trick. If you specify the variable as an array, it will always be treated as such.

For example, instead of

    return $mylist
...you can...

    return @($mylist)
The return keyword is unnecessary in PowerShell so this can be shortened to:

    @(mylist)


Yow. That's a great answer . . . for some value of "I wish I didn't have to know that, but I do have to, so therefore it is great." :-)


> "It's a miserable language, full of unexpected behaviors and badly designed features."

Yes, powershell is an insufferable language. It is in good company with every other language ever invented by humans.

"Unexpected behaviors and badly designed features." Are you talking about ... C? PHP? Javascript? Java? Ruby?

The design/implementation faults in C, for example, come at a cost of billions of dollars a year in damages (and that doesn't include the cost of mitigation).

Sure, Powershell has plenty of warts, point me to a perfect language that does what Powershell does as well as it does but without the warts and then people can stop using Powershell.


What are you comparing it to? Compared to Bash, I love the fact that everything gets returned as an object that you can query, rather than as a blob of text that you have to hack on.


You're mistaken. Bash functions and unix commands return an exit status (an integer), never text.


That's for status, not data. A time in bash is a set of characters, not a time.


All the shell does is allow the user to redirect input and output streams to/from system commands. Whether these commands output/read text or binary data or both is irrelevant from the shell's standpoint.


The whole unwrapping the list sound like bugs I hit trying to implement a lisp interpreter.


Or, the language designer thinking he's saving programmers work by jus returning the value in cases where there is a list of length one.

However, he's really making it worse because now all return values from that function have to be type-checked to see whether they are scalars or lists.


R comes to mind, where once in a blue moon a matrix result will happen to only have a single column, which R helpfully decides should be turned into a simple vector, which then doesn't have the matrix operations available anymore (like dim()), and kaboom!


The classic story of every interpreted language.


Yeah. I learned LISP pretty early in my career, wrote a few LISP interpreters, and those are indeed the kinds of bugs that I was stomping. PowerShell seems to have turned those bugs into language features. Augh.


You would've loved VBscript lol




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: