Enforced Password Change at next logon attempt - Windows

If your AD account has the “User must change password at next logon” option enabled:
clip_image001
and you try to logon to a RDP session (with correct credentials):
image
you might encounter this error message:
image
“You must change your password before logging on the first time. Please update your password or contact your system administrator or technical support.”
This is a classic catch 22 issue: You have to logon to change you password, but you cannot logon until you’ve changed you password.
If you have access to a “normal” network connected Windows client you can change the password that way, but what if you only have RDP access?

Client side

Well, if the server allows it, you can temporary disable “Credential Security Support Provider (CredSSP)” in the RPD client. This disables Network Layer Authentication, the pre-RPD-connection authentication, and therefore enables you to change your password via RDP. CredSSP is enabled by default in the RDP client on Windows Vista and forward.
There is no option to disable CredSSP in the RDP client, so here is how you have to do it:
  • Start mstsc.exe
  • Click Show Options
  • Click Save As
image
  • Call it ChangePassword.rpd (or anything you’d like, but avoid the name Default.rdp)
  • Open the saved ChangePassword.rpd in Notepad
  • Add a new row at the end with the following text:
    enablecredsspsupport:i:0
clip_image003
  • Save the rdp file
  • Double-click the rdp file
  • Enter the name/IP of a domain connected computer with RDP enabled
Instead of the local Windows Security prompt (the second image in the blog post) you should see a Windows Logon screen on the remote computer (if not, read on anyway):
image
If the account you log on with at this point has the “User must change password at next logon” option enabled, you get notified about that:
image
By clicking OK you get the possibility to change the password (yay!):
image
After changing the password you get confirmation about the change:
image´
Clicking OK logs you in.
In fact, you do not need to have access to sign in through RDP, in that case this shows up, but only after you successfully changed your password:
image
Delete the ChangePassword.rdp file when you are done (or at least do not use it until you are forced to change your password again), since disabling CredSSP lowers the security of RDP connections.

If the server requires CredSSP

If the server does not allow you to disable Credential Security Support Provider, you get this error message when connecting:
image
In that case, try connecting using the FQDN (DC01.tomdemo.se and not only DC01) or connect to other servers that might allow you to disable CredSSP. As I mentioned above, you don’t have to have access to actually logon to the server.

Server side

You can also disable CredSSP on the server side, but since that lowers the security on all RDP connections to that server it is not recommended.
If you chose to do this anyway, you do it either by de-selecting “Allow connections only from computers running Remote Desktop with Network Level Authentication (recommended)” in System Properties:
image
Or if you run the Terminal Server Role:
  • Open Terminal Server Configuration
  • Open RDP-Tcp configuration page
  • On the General tab, set the Security Layer to RDP Security Layer
image
Note that if you already have an existing access to a server (with the account you need to change the password with) you could just change your password in that session by pressing Ctrl-Alt-Del (or Ctrl-Alt-End in an RDP connection) and choosing Change a password:
image
I hope this post helped.

How to Install Zenmap - Kali

In Kali 2019.4 zenmap is no longer installed by default because it wasn’t maintained upstream so the package was dropped.
Installing “apt install zenmap” didn’t work as we see below.
apt-get install zenmap
Reading package lists... Done
Building dependency tree
Reading state information... Done
Package zenmap is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
However the following packages replace it:
ndiff


E: Package 'zenmap' has no installation candidate
So we need to download the zenmap .rpm on there website https://nmap.org/download.html
Next install “Alien” and convert the .rpm to a .deb file we can install with the dpkg command.
apt-get install alien
Convert .rpm to .deb
root@kali:~/Downloads# alien zenmap-7.80-1.noarch.rpm 
zenmap_7.80-2_all.deb generated
Final step is installing the created zenmap_7.80-2_all.deb file.
root@kali:~/Downloads# dpkg -i zenmap_7.80-2_all.deb 
Selecting previously unselected package zenmap.
(Reading database ... 323539 files and directories currently installed.)
Preparing to unpack zenmap_7.80-2_all.deb ...
Unpacking zenmap (7.80-2) ...
Setting up zenmap (7.80-2) ...
Processing triggers for kali-menu (2020.1.4) ...
Processing triggers for desktop-file-utils (0.24-1) ...
Processing triggers for mime-support (3.64) ...
Processing triggers for man-db (2.9.0-2) ...
Zenmap should now be installed and available in the start menu.
Zenmap startmenu
Zenmap running

Critical Security Control : Removing local administrators once and for all

One of the most important security control is limiting administrator permissions as not all users require them to perform their daily tasks. Additionally, with spear-phishing campaigns or exploit-landing sites, it’s easy to take advantage of privileged user accounts that make it easier for malware to write to any folder location, or write to registry paths (file-less malware).Before we dive into the rest of the article let’s go over removing Local Administrator rights for Domain systems (Since this is probably what you were looking for to begin with).

Removing Local Administrators through GPO

A good and effective way to remove your users from being local administrators is to create and apply a group policy object to your computer OUs.
Launch Group Policy:
  1. Right click your computer OU and
  2. Create GPO in this domain, and link it here
  3. Provide a name (RemoveLocalAdmins) , click OK
  4. Right click your newly created GPO RemoveLocalAdmins and select Edit
  5. Navigate to Computer Configuration > Preferences > Control Panel Settings Local Users and Groups 
  6. Right-click anywhere and All Tasks > AddAction: UpdateGroup Name: Administrators (built-in)
    Delete All member users (Checked)
    Delete all member groups (Checked)Click OK 
This will ensure that all Local users and groups are removed from the Administrators group. Next, you may re-add the built-in Administrator account and specify a domain group (or not) to be part of the local administrator group.
  1. Right click your newly created GPO RemoveLocalAdmins and select Edit
  2. Navigate to Computer Configuration > Preferences > Control Panel Settings Local Users and Groups 
  3. Right-click anywhere and All Tasks > AddAction: UpdateGroup Name: Administrators (built-in)Click on Add…
    Members: Administrator
         
    (Note: when you click Add, you can type Administrator without having to browse for it, it works)

    Click OK
You should see something similar to the screenshot below. You may add a description to help your fellow sysadmins.
Exit the Group Policy Manager editor. Now you have finally taken your user rights away.Cheers!

Why do we need local administrator rights?

I won’t go into detail but rather make a few points of why your organization still has everyone as a local administrator.
  • “Users have always had it, and it would disrupt their workflow if you took it away” – X Manager
  • Legacy software that needs to have local administrator rights (and possibly AV exclusions as well) – Oil & Gas, Accounting/Finance software
  • C-level and other executive folks…because they can.
  • Your IT department is too lazy to package applications and deploy them through (insert Application deployment Name here e.g (BigFix, KACE, SCCM), and would rather let users install it themselves.
  • Because everyone still has that old mentality and think that they need it to add printers.
  • [Insert additional excuses here]

A solution to this bad practice:

Here’s my approach when dealing with these type of scenarios and once and for all removing administrative rights away.
  1. Understand your environment and know exactly what applications are running and when.
  2. Get upper-management support. Ensure that you explain the risks of having Local Administrator rights. (Show them proof). I will discuss this in more detail.
  3. Select a good population of representative users to test with (i.e Admins, Business Analysts, Developers, Accountants). Limit their rights and have them provide you feedback on any issues they might run.
  4. Based on the feedback you collect, create Group Policy-based exclusions and apply them for those computer Organizational Units (OUs), and ensure they work properly on 32-bit & 64-bit.
  5. Set a deployment schedule and follow it! I would do this based on departments (Week 1: Accounting, Week 3: HR, Week 5: Finance:) or Monthly for those one-offs you might have missed.
  6. Get your IT staff to follow a checklist in which newly re-imaged systems are moved to the appropriate OUs, or automate it.
  7. Follow up with upper management about new applications, user feedback and show post-change metrics.


Point 1: Understand your environment

If you have no visibility of what you are trying to protect then you cannot protect it! One of the basic concepts of the Critical Security Controls is to have a way to inventory your assets, which includes software. Understand what software is running in your environment and who is running them. Using our asset management software I am able to query software that is installed based on specific systems. Additionally, I am collecting Windows Event Logs (Sysmon) real-time which allows me to see exactly what software is being launched and when.
This will provide me with applications that might be non-standard and allow me to investigate the requirements for those applications. Additionally, this will also shed some light on applications that are not frequently used (Think of Accounting software that gets used only at the end of the month during closing time), as someone will eventually call you because you didn’t consider everything.
Once you know what’s running and what’s considered “normal” about your environment, you will also start noticing the “abnormal”.

Point 2: Get Upper-Management support

As I mentioned just now, you will also notice the “abnormal” which includes Adware, Games, and other 3rd party software (toolbars!) that users have installed over the years with their privileged accounts. Gather this information and use it as proof to justify the removal of administrator rights when talking to management. Ensure that you explain the risks of having local administrator rights for users and how this is a bad practice in most organizations. I would cover things such as Spear-phishing campaigns (because those target anyone) and the ease in which malware could run, specially with someone having administrative rights. I would also provide a report with software used by each department to prevent those counter-arguments of “not knowing what we need administrator rights for, but we definitely need it”. At the end of the day, the last thing you want to do is prevent people from performing their work; therefore, ensure that you approach them in such a way that you make them think about the company first and understand the risks that “we” all face as a company. Remember that we’re all a team, and as a Security professional you must be able to work well with other departments and provide accurate information.

Point 3: Select a “representative population” or Power Users

After getting the green light from upper-management, you’ll be too excited about limiting user rights; however,this is the time to think about how you will approach this situation. Talk to the Desktop support team and the Managers of each department to carefully select the “Power users”, those that are savvy and will provide you with the feedback needed after their rights have been removed. Give yourself a few weeks to ensure that you have translated those needs into Group Policy exclusions for software which should be limited to Folder Rights and Registry rights. 
I can’t give enough credit to the Sysinternal tools, and one of the most useful tools to figure out why an application doesn’t run with limited user access is ProcessMonitorRun this application (run as Administrator, I know Ironic!) while you run an application that doesn’t work with limited user rights. It will give you the following information
  • Process Name – The application or dependent process running
  • Operation – What action is that application/process attempting (Creating a file, Reading registry/writing to registry)
  • Path – Self-explanatory
  • Result- You will see results such as ACCESS DENIED which will be good indicators of where your exclusions need to be made.
  • Detail- In conjunction to the Operation you will know what type of access the application might need (Write/read/modify).
This tool will enable you to create your GPO with those application-specific exclusions.

Point 4: Create Group Policy Objects

At this point you have gathered enough information and know exactly what Folder permissions need to be granted in addition to any registry paths.
Launch Group Policy:
  1. Right click your computer OU and
  2. Create GPO in this domain, and link it here
  3. Provide a name (Non-Admin) , click OK
  4. Right click your newly created GPO Non-Admin and select Edit
  5. Navigate to Computer Configuration > Windows Settings > Security Settings > File System
  6. Right click anywhere and Add File 
  7. Browse or type the desired path then click OK . (Note: Ensure that you consider both 32-bit & 64-bit OS paths)
  8. Select your appropriate group (in this example: Domain Users, or Accounting Users/Finance/Users, or Individual user), and click OK.
  9. Depending on how granular you want to get with the folder permissions, choose the appropriate one.
    In this case we’re selecting: “Replace existing permissions on all sub-folders and files with inheritable permissions). Click OK
Even if you give the domain users full rights to write/execute to and from a specific folder, you can take it a step further and white-list specific applications expected to run from that directory using Applocker (if running Windows Enterprise version), so you may sleep better at night.
Registry Settings
1. On the left pane you will see an option for Registry. Click on it.
2. Right click anywhere and Add Key. Browse or manually enter the path for the registry.
3. Add the appropriate permissions for the registry and click OK.
4. Again, choose the appropriate way to propagate these permissions. In this case “Replace existing permissions on all subkeys with inheritable permissions) then click OK
5. Done! you may exit the Group Policy Management Editor.
On your Power User system, you  may run a gpupdate /force command to force a GPO update and apply the settings.
Re-launch the legacy application and see if it works, otherwise re-run ProcessMonitor.

Point 5: Create a deployment schedule and stick to it

After a few weeks/months of testing with your power users, you should now be confident on the findings and exclusions you have set in place in your Group policy. Now create a schedule and start moving department systems to the “No-Admin GPO”.
My suggestions:
  • Provide enough time to remediate issues that might come up. (Wait a few weeks before you start with moving another department)
  • Review Application logs for potential “Access denied” logs when updates are ran
  • Ensure that GPO is being applied to all of your endpoints

Point 6: Create a checklist or automate the process for re-imaged systems

Six months from now you will realize that there’s still a high amount of local administrators even though you took the initiative to move most endpoints to the appropriate OUs. Most of these systems are newly re-imaged systems that your Desktop support team or Sysadmins built and joined to the Domain without moving them to the appropriate OUs. You need a way for them to follow a checklist and ensure that no domain user is added as the local administrator, and that the system will be moved to a restricted OU before it is provided to the user.
If you’re like me, you’d like to automate this process, here’s a few options:
  • If you sysprep your systems, you should be able to add the OU information to the configuration file
  • SCCM or KACE will also allow you to move to a specific OU post re-image phase
  • Write a powershell script that will move newly-joined Domain systems to the appropriate OUs
Ensure that you have a discussion with other IT groups so that everyone is on the same page; otherwise, your help desk will continue to add domain users as administrators and your efforts will fail.
Update 09-26-2017 (powershell script)
Here’s a powershell script that you may use to move your Workstations/servers to a specific OU in which you have implemented the Non-Admin GPO discussed earlier. This will ensure that all of your newly domain-joined systems do not have administrator rights. This script may be added as a scheduled task and set to run at whatever interval you want (I run mine daily)

#This script grabs the OperatingSystem information from the default Computers container and if they match a- 
#-Certain Server Operating system, they will be  moved to the Domain Computers\Servers only.
$a = Get-ADComputer -filter * -SearchBase "CN=Computers,DC=domain,DC=com" -Property OperatingSystem
Foreach ($os in $a) 
{
if ($os.OperatingSystem -match "Windows 7*") 
{ $os | Move-ADObject -TargetPath "OU=Non-Admin-Workstations,DC=domain,DC=com"
}
#Added a Powershell comment block, if you need to move a specific version of Windows, just Uncomment it. 
<# Elseif($os.OperatingSystem -match "Windows Server 2003")
{ $os | Move-ADObject -TargetPath "OU=Servers,OU=Domain Computers,DC=domain,DC=com"
}
Elseif($os.OperatingSystem -match "Windows Server 2008 *")
{ $os | Move-ADObject -TargetPath "OU=Servers,OU=Domain Computers,DC=domain,DC=com"
}
Elseif($os.OperatingSystem -match "Windows Server 2012*")
{ $os | Move-ADObject -TargetPath "OU=Servers,OU=Domain Computers,DC=domain,DC=com"
}
Elseif($os.OperatingSystem -match "Windows Server 2016*")
{ $os | Move-ADObject -TargetPath "OU=Servers,OU=Domain Computers,DC=domain,DC=com"
}
Elseif($os.OperatingSystem -match "Windows Storage Server*")
{ $os | Move-ADObject -TargetPath "OU=Servers,DC=domain,DC=com"
} 
Elseif($os.OperatingSystem -match "Windows Embedded Standard*")
{ $os | Move-ADObject -TargetPath "OU=Servers,DC=domain,DC=com"
}
Elseif($os.OperatingSystem -match "Windows NT*")
{ $os | Move-ADObject -TargetPath "OU=Servers,DC=domain,DC=com"
}
#>

}

Point 7: Revisit with Upper-Management 

After user complains have significantly decreased to a few calls every other week, you should now pat yourself in the back and look at the progress that you have made. Re-run your reports looking for 3rd party software, toolbars, malware objects and see if your environment is doing better than it was when you initially started. Report this to upper management and thank them for their cooperation as it is difficult to get people on-board most of the time.
You did great! but remember…
Business operations will continue to evolve, and new software vendors will come into the picture. You want to ensure that your IT staff has ways to deploy new software (Using Application deployment tools such as Big Fix, KACE, SCCM, etc); otherwise, that whole department will need to have local administrator rights to install the software.

How to learn the proper way

To thrive in today’s world of disruption and rapid change, your ability to learn fast will be your hidden advantage.

Our learning abilities define our capacity of perform which in turn define our worth.

We currently learn by in the following path

1. Learn (Input)

Pay attention without distractions. Isolate your mind to what you are learning at the moment.
Do not multi task.

2. Reflect

Draw or document what you have learned and how you can use it in the real world.

3. Implement (Output)

Try practicing what you have learned to provide your self proof of concept.

4. Share

Teach or contribute with others to help recognize and build accreditation.

We should remember to practice what ever we have learned by creating an output or implementing it. Sharing with other people can also help develop the skill. We need to spend twice as much time spent to learn it.
https://www.youtube.com/watch?v=ZVO8Wt_PCgE

Windows PowerShell Cheat Sheet

Windows PowerShell is the successor of the MS-Windows cmd language, which itself has its roots in the ms-dos Bat language. All recent versions of Windows offer PowerShell (PS). PS may be seen as Microsoft's answer to the shells common in Unix/Linux (such as Csh, Bash, etc.). Its name implies that Microsoft sees the shell as powerful, which it arguably is.
In these notes some important PS commands are listed and PowerShell's most notable feature, the object pipeline, is discussed. From the outset it is important to note that, in contrast to Linux/Unix, Windows PowerShell is completely case-insensitive.
Most monospace text snippets below are valid PS and may be copied, pasted, and executed in a PowerShell- or a PowerShell_ISE-session. This is why the notes form a "Cheatsheet". As is common for cheatsheets, there is hardly any explanation, the examples speak for themselves. It must be stressed here that many of the basic PS commands are not at all orthogonal, so that many variant pipelines can lead to the same effect. The examples just give one out of the several possibilities to accomplish a certain task.
The last two sections are about search in and traversal of the Windows Registry by means of PowerShell.

1Unrelated to PS

A few lesser known windows features and tricks, not directly related to PowerShell:
  1. In the Windows (File) Explorer address bar enter cmdpowershell.exe, or powershell_ise and a corresponding window opens at the current directory. This is most likely the shortest route to opening a cmd, ps, or ps_ise session from a given directory.
  2. Typing Control Panel (nl: Configuratiescherm), Regeditpowershell, ... after hitting the start or search button in the Windows taskbar opens a screen with the name of the program. Click on it once and the program opens.
  3. In PS: Get-WmiObject win32_useraccount gives the SID's (security identifiers) of accounts on present computer.
  4. Adaptation of user environment variables: Control Panel/User Accounts/User Accounts/. Then left panel: change my environment variables.
  5. Adaptation of system environment variables: Control Panel/System and Security/System/Advanced System Settings. Button: Environment Variables. Set in panel system variable not in administrator account. Change is persistent, a change (see below) within PowerShell is volatile.
  6. In a cmd session: setx sets persistent user environment variable. To unset: go to HKCU/environment (in the Registry) or to Control Panel/User Accounts.
  7. In a cmd session: doskey np = notepad++ $* sets alias for notepad++.exe fn.ft (provided notepad++ is in path)

2About ISE

ISE stands for Integrated Scripting Environment. ISE—a standard part of Windows 10—is an editing and execution environment for PowerShell.
An important advantage of the use of ISE over pure PowerShell, is that ISE offers pop-up help: when the user is typing a command line, ISE often pops up a relevant list of methods and properties. By double clicking on an entry in the pop-up list, the entry is appended to the command line.
A few noticeable points:
  1. Upon the start of ISE, the script Microsoft.PowerShellISE_profile.ps1 is executed.
  2. To protect ordinary users against malignant PS scripts, the execution of PowerShell scripts (including the ISE profile) must be allowed explicitly. This is done once and for all by issuing the command
                  Set-ExecutionPolicy RemoteSigned
                      -Scope CurrentUser
    
  3. Allow execution of a script downloaded from the internet by:
                  unblock-file .\script.ps1
    
    where script is the name of the downloaded script (which is in the current directory).
  4. Execution of a script requires that the scriptname is prefixed by its path. If the script is in the present folder use .\script.ps1, where script is the name of the script.
  5. Set width and height of the output pane by:
                  $Host.UI.RawUI.BufferSize = New-Object
                      $Host.UI.RawUI.BufferSize.GetType().Fullname (150, 40)
    
    (The pure PS console does not allow shrinking of width).
  6. Set ISE console fontsize by:
                  $Host.PrivateData.FontSize = 13
    
    (pure PS has a different $Host.PrivateData object).
  7. Start PowerShell as administrator:
                  Start-Process PowerShell -Verb runAs
    
  8. An important ISE tool for PowerShell learners is CTRL-J. This pops up a selection of code snippets. These are templates that give illustrative examples of PS syntax and can be used in scripts as a start points for further editing.

3Notepad++

Provided the correct path is set, notepad++.exe start the Notepad++ editor from a PS session. Or set an alias by: set-alias -name np -value "C:\Program Files (x86)\notepad++\notepad++.exe" and then np opens Notepad++.

4Cmdlets

The internal commands of PowerShell are called "cmdlets". A cmdlet name is of the form "verb-noun", where "verb" is one out of a fixed set of verbs. All cmdlets return an object. For instance,
      Get-ChildItem .\
returns an array object of which the elements are objects belonging to the children of the current folder (.\). If the whole object is to be inspected, pipe it to Get-Member (see below). Any cmdlet flag (= parameter) can be truncated to the extent that it is still unique. A few examples of cmdlets and their flags:
   Get-Help                   # Gets help about a cmdlet,
                              # example: get-help get-help
   Get-PSdrive                # List PSdrives, such
                              # as c:, env:, hklm:, hkcu:, alias:, etc.
   Get-ChildItem              # In Registry: children are subkeys
                              # and value entries in current key.
   Get-ChildItem              # In File System: children are
                              # subdirectories and filenames in current directory.
   Get-ChildItem -recurse     # Lists all children, grandchildren, ...
                              # of current PSdrive, directory, or Registry key.
   Get-ChildItem -rec -force  # Include hidden directories
                              # (flag -hidden searches hidden directories only)
   (Get-ChildItem .\).name    # List names ('name' is an object property)
                              # of files and directories in current directory.
   Get-ChildItem  -name       # Equivalent to
                              # (Get-ChildItem .\).name
   (Get-ChildItem .\).count   # Number of entries in the array object returned by Get-ChildItem
                              # (count is a property of an array).

5GoTo a PSdrive

To switch a PS session to another PSdrive or directory and get the children of the new location, proceed as follows:
To env:
   Set-Location env:          # Prompt character
                              # becomes PS Env:\> (environment variables)
   Get-Childitem              # Get all environment
                              # variables
   Get-Childitem userprofile  # Get environment variable
                              # userprofile=C:\Users\user_name
To alias:
   Set-Location alias:        # To PS-Drive alias
   Get-Childitem              # All aliases
Switch to c:\users and get a named child from another PSDrive:
   Set-Location c:\users      # Back to filesystem c: (
                              # directory users)
   $env:userprofile           # Get environment variable
                              # (note the $ in $env:) userprofile=C:\Users\user_name
   $alias:ls                  # Get what alias 'ls' stands for
                              # --> Get-ChildItem  (Note the $ in $alias:)

6Pipelines

Important: Cmdlets pass objects through pipelines, not a character stream as in Unix.
The pipeline character is |, it must be followed by a cmdlet. The passed pipeline object is referred to by the automatic variable $_ and its member member_name is accordingly referred to by $_.member_name.

The treatment of the passed object $_ depends on the cmdlet it is passed to. To illustrate this, it is noted that a PowerShell string is an object with many methods, one of them being .contains("substr"). The method returns true if the string contains "substr" and false if not. In the following example the stuff between curly brackets is a script block which is a sequence of executable statements. Now pass the very same string object to two different cmdlets:
    "a string" | Foreach-Object {$_.contains("ri")} # -> True
    "a string" | Where-Object   {$_.contains("ri")} # -> "a string"
In both commands it is tested whether "ri" is a substring of "a string" (which it is). The first cmdlet passes on the resulting boolean value which drops off the end of the pipeline and is then sent to the console. The second cmdlet uses the boolean result to decide what to do: if true the string is passed on in the pipeline (and as it is at the end it is sent the console). If false where-object stops the pipeline and no console output is generated.

The cmdlet Get-ChildItem not only gets children of a directory (files and subdirectories), but also an array of children of any PSdrive:
    Get-ChildItem alias:                       # All children of alias: all defined aliases
In the next three pipelined statements it is assumed that all elements (also objects) of the array passed through the pipeline have a member called name. The name name must be known by the cmdlet that handles the pipelined input.
    Get-ChildItem alias: |Where-Object {$_.name -eq 'ls'}# Gives: "Alias  ls -> Get-ChildItem"
    Get-ChildItem alias: |Where-Object name -eq 'ls'     # Gives: "Alias  ls -> Get-ChildItem"
    Get-ChildItem alias: |Where-Object name -match 'ls'  # Gives three matching aliases (ls, sls, and cls).
The second and third commands do not use a script block but the short-hand name. Shorter, use Get-Alias (no pipeline):
    Get-Alias ls     # Gives: "Alias  ls -> Get-ChildItem"
    gal ls           # Gives same.

The names of members (methods and properties) of an object are obtained by piping the object to Get-Member. Example:
    Get-Process PowerShell_ise |Get-Member   # Short: ps PowerShell_ise |gm
This gives:
       Name                       MemberType     Definition
       ----                       ----------     ----------
       Handles                    AliasProperty  Handles = Handlecount
       Name                       AliasProperty  Name = ProcessName
                            ...
       GetType                    Method         type GetType()
       InitializeLifetimeService  Method         System.Object InitializeLifetimeService()
       Kill                       Method         void Kill()
                            ...
       Id                         Property       int Id {get;}
       MachineName                Property       string MachineName {get;}
                            ...
       ProcessName                Property       string ProcessName {get;}
       StartTime                  Property       datetime StartTime {get;}

       ...
Knowing property names we can pass them to Select-Object to get their values,
    Get-Process PowerShell_ise |Select-Object name, id, processname, starttime  # names separated by comma's
This gives:
       Name              Id  ProcessName     StartTime
       ----              --  -----------     ---------
       PowerShell_ise  6732  PowerShell_ise  12/14/2017 11:05:47 AM
The cmdlet Where-Object acts as a filter, it transfers objects through the pipeline that satisfy a boolean condition. The cmdlet Get-Process gets objects for all running processes.
    Get-Process |Where-Object name -match  PowerShell_ise
                |Select-Object name, id, processname, starttime
This gives again:
       Name              Id  ProcessName     StartTime
       ----              --  -----------     ---------
       PowerShell_ise  6732  PowerShell_ise  12/14/2017 11:05:47 AM
Many of the property names and their values are obtained by piping an object to Format-List *. Methods are not listed.
    Get-Process  PowerShell_ise | Format-List *
This gives:
       Name                       : PowerShell_ise
       Id                         : 6732
                        ...
       StartTime                  : 12/14/2017 11:05:47 AM
                        ...
       UserProcessorTime          : 00:00:46.3750000
                        ...
       Container                  :

7Useful aliases

Many cmdlets have one or more aliases. Often an alias is DOS- or Unix-like.
    ac = Add-Content                 # Example: ac -value 'The End' -path 'flop.txt'
                                     # (appends value to file)
    cat = gc = type = Get-Content    # Get the content of a file;
                                     # returns an array with one line per element
    cd = sl = Set-Location           # Change folder, Registry key, or PSdrive.
                                     # Example: cd env:, cd HKLM:
    cls = clear = Clear-Host         # Clears console
                                     #
    dir = gci = ls = Get-Childitem   # List children in current
                                     # PSdrive/folder/Registry key
    echo = write = Write-Output      # String to output array. Array is sent to console, into
                                     # pipeline, or redirected/appended to file
    foreach = % = Foreach-Object     # Only in pipeline: for each object crossing the pipeline
                                     # Do not confuse with language construct of the same name
    ft = Format-Table                # Only in pipeline.
                                     # Example: ls *.jpg |ft directory, length, name -AutoSize  -Wrap
    fl = Format-List                 # Only in pipeline. Example: ls env:\Path |fl
                                     # (gives wrapped output of environment variable "Path")
    gal = Get-Alias                  # "Get-Alias -definition cmdlet", gives aliases of cmdlet
                                     # "Get-Alias [-name] alias", gives name of cmdlet called by alias
    gcm = Get-Command                # Get all commands (cmdlets, functions, and aliases).
                                     # gcm -CommandType Alias -> all aliases
    gm = Get-Member                  # Example: ls flop.txt | gm
                                     # (all members of object flop.txt)
    gp  = Get-ItemProperty           # In file system: gp * gives same output as ls *
                                     # In Registry: value entries (names and values)
    gpv  = Get-ItemPropertyValue     # In filesystem: get prop's of files. Ex: gpv *.txt -name basename (names of .txt files)
                                     # In Registry: get value of value entry. Example: gpv -name cursorsize (returns number)
    gv = Get-Variable                # Get names and values of
                                     # all session variables
    ps = gps = Get-Process           # List running processes
                                     #
    pwd = gl = Get-Location          # Current directory (folder)
                                     # or Registry key
    ren = rni = Rename-Item          # Examples: ren report.doc report.txt
                                     # and: ls report.doc | ren -newname report.txt
    rv = Remove-Variable             # Remove variable (name without $ prefix, while
                                     # note that variable names must begin with $)
    select = Select-Object           # Select specified properties of piped object
                                     # Example: ps |select Processname | select -first 10
    sleep = Start-Sleep              # Sleep -sec 1
                                     # (sleep 1 second)
    sls    = Select-String           # Example: sls foo.txt -patt '^\S' (a regular expression
                                     # giving all lines that do not start with blank, tab, or EOL)
    where  = ? = Where-Object        # Only in pipeline.
                                     # Example: ls -recurse |? name -like '*Pict*'

8Example of a pipelined command

The following pipelined command may be issued from C:\Program Files, for example. It outputs the names of .dll files of size less than 10000 bytes in the directory and all its subdirectories:
    Get-ChildItem -recurse -path *.dll | Where-Object {$_.length -lt 10000} |
       Sort-Object -property Length | Format-Table -property name, length, directory -wrap
The flag -path, being default, can be omitted. The command can be shortened further by introducing aliases and abbreviated flags. For clarity, the statement is split by assigning an array object to the variable $a. (Recall here that variable names are text strings that begin with a dollar sign). Note that the cmdlet Where-Object = ? can recognize names of object properties without use of a script block. That is, {$_.length -lt 10000} is equivalent to length -lt 10000. Thus,
    $a = ls -r *.dll |? length -lt 10000  # Store in $a all .dll files from current directory downward
                                          # with file sizes < 10000 bytes
The array object $a is piped to the alias sort of the cmdlet Sort-Object and the sorted object goes to Format-Table:
    $a | sort length | ft  name, length, directory -w # Sort entries of array $a on file size (length) and tabulate
                                                      # formatted name, length, and directory
Finally, in one statement:
    ls -r *.dll |? length -lt 10000 |sort length |ft name, length, directory -w

9More examples of pipelines

An alias is not the same object as the corresponding cmdlet. This is proved by the inspection of the output of the following:
    gcm ls            | fl *   # All properties of the alias 'ls' are listed
    gcm Get-Childitem | fl *   # All properties of the cmdlet 'Get-Childitem' are listed

List properties (names and values) of the file object PSnotes.txt:
    ls PSnotes.txt |fl *                          # Lists all properties: Directory, LastAccessTime, Basename, etc.
    ls PSnotes.txt |fl LastAccessTime, Basename   # Lists two properties: date/time of last access and file name.
If you want to tabulate names and values of one or more properties then pipe to ft. Example:
    ls PSnotes.txt |ft LastAccessTime, Basename   # Tabulates date and time of last access and file name
List the content of the lines in foo.out that begin with four or more spaces together with their sequence numbers:
    sls foo.out -patt '^[ ]{4,}'|ft linenumber, line
(Note that an empty line may not contain spaces and is then not shown by this command).
The difference between tabulating (ft) and listing (fl) properties is minor.

An example of ?=Where-Object: select the basenames (file names without extension) of files in current directory that end with the letter r (use of operator -match with a regexp):
    ls  |? basename -match 'r$'
This lists the matching basenames plus additional information (mode, write time, length, full file name). If only the basename must be listed, use:
    ls  |? basename -match 'r$' |ft basename
Other examples of ?. Use of comparison operators -in, -notmatch, -and, -notlike. The first example limits the list of 'svchost' and 'firefox' processes to the first 10. The second example uses a script block, which is the code snippet between curly brackets:
    ps |? ProcessName -in  "svchost", "firefox" | select -f 10 |ft processname,  PagedMemorySize
    ls |? {$_.name -notmatch 'e$' -and $_.name -notlike 'c*'}
       # (Names not ending on "e" (regexp) or beginning  with "c")
How long did Notepad++ run? The cmdlet Format-Table recognizes as parameter a hash table (@{..;..}) with entries L(abel) and E(xpression) defining the header and entries of the table sent to the PS console:
    ps Notepad++ | ft ProcessName, @{L="WallClock time"; E={(Get-Date) - $_.StartTime}}
This gives something like:
       ProcessName   WallClock time
       -----------   --------------
       notepad++     07:44:35.0765408
The number under the heading 'WallClock time' is the result of the expression (an elapsed time).
As a last example, the string 'was' is found in all .txt files in the present directory by application of Select-String = sls:
   sls *.txt  -pattern 'was' |ft -wrap filename, linenumber, line
Find all directories called winx from the present directory downward (-rec). Inspect also hidden directories (-force) and suppress error messages (-ea 0):
    ls winx -dir -rec -force -ea 0 |ft

10Examples foreach

Remember that % is an alias for ForEach-Object, as is foreach.
    Get-Alias   |% {if ($_.name -match '^s') {Write-Host $_.name, $_.definition}}
Get-Alias sends 158 objects (aliases) through the pipeline. All objects have a property name, which is matched against a regexp. The regexp checks if the first letter is 's' or 'S'. If so, the properties name and definition of the current object are written to screen. The body of the Foreach-Object must be enclosed by curly brackets (the outer ones), and so must the body of the true branch.
Note that a list of aliases starting with 's' or 'S' can be obtained from shorter Where-Object statements:
    Get-Alias |? name -match '^s'   # regexp
    Get-alias |? name -like 's*'    # string + wildcard 
And still shorter:
    gal s*

Count number of lines of each member of a list of .txt files (every filename followed by single space and number of lines):
    ls *.txt  |%   {Write-host ($_.name, " ") -NoNewline;  (cat $_).count } 
Remember that cat returns an array object that has the property count.

The present file, called PS_cheat_sheet.html, contains several terms in bold font, surrounded by and . The following PS pipeline extracts (almost) all bold text strings from it that occur first on a line. (The command does not work correctly when bold text extends over different lines):
    sls -path PS_cheat_sheet.html -pattern '' |% {$_.line -match '(.*?).*' |out-null; $matches[1]}
The sls (select-string) command returns all lines containing . Next, on each line the first arbitrary string enclosed by  and 
 is captured. The string to be captured is indicated by round brackets around .*?. The question mark indicates "non-greediness", at most one match is found on on each line. The output (true or false) of the match expression is sent to /dev/null, in powershell: out-null. The capture is caught in $matches[1], the second element of the automatic array $matches.

An example of foreach as a language construct:
    $letterArray = "a","b","c","d"
    foreach($letter in $letterArray){
       Write-Host -ForeGroundColor green $letter
    }
Yet another example as language construct:
    $ff = ps firefox                  # Usually there is more than one firefox process active
    foreach($p in $ff) {$p.starttime}
This gives something like (in Dutch):
    zaterdag 8 juni 2019 07:47:44
    zaterdag 8 juni 2019 13:53:41
    zaterdag 8 juni 2019 07:47:30
    zaterdag 8 juni 2019 07:47:28
    zaterdag 8 juni 2019 15:21:39
    zaterdag 8 juni 2019 13:42:46
    zaterdag 8 juni 2019 11:52:45
Incidentally, the very same output is obtained by:
     ps firefox |select-object -expandproperty starttime
The flag -expandproperty of select-object (alias: select) expands the date in property starttime to a long notation.
Bracketed subexpressions of a regular expression are captured in the automatic array $matches and foreach can loop over this array. To get a sorted list of the unique verbs of all aliases, issue:
    gal|? displayname -match '->(.*)-.*' |% {write $matches[1]} |select -Unique |sort

11Datatypes and strings

Type cast operators are among others: [int], [long], [string], [char], [bool], [byte], [double], [decimal], [single], [array], [xml], [hashtable], [PSCustomObject].

Once a variable is declared explicitly, an implicit type change is forbidden. Examples:
    [string]$s = 'Peanuts'    # Explicit declaration
    $s         = 4            # Assign new string '4'
    $s * 3                    # Gives 444 (triplicates the string)
    [int]$s    = 4            # Explicit change of type (to int32)
    $s * 3                    # Gives 12
    $s         = 'bear'       # Error --> Cannot convert value "bear" to type "System.Int32"
    [string]$s = 'bear'       # Explicit recasting is OK
    $s = [char]0x07           # Recasting is OK on RHS (hex-char 0x07 sounds "Bell", warning sound)
Another type casting example:
    'a', 'b', 'c', 'd' > letters.txt       # Each letter on a line in file letters.txt. Output in UTF-16 (!)
    [string]$letter = cat letters.txt      # Get-Content, assign to string
    $letter                                # --> a b c d   (a single string)
    $letter = [string](cat letters.txt)    # The same. Brackets are necessary to cast the output of the cmdlet
Use set to assign a constant:
    set pi 3.14 -opt const    # Assign constant;  (No $ in set pi !)
    $pi                       # Gives 3.14       (Use $ in reference to the constant just set without $!)
    $pi        = 2            # -> Error: Cannot overwrite variable pi because it is read-only or constant.
    set pie $([math]::pi) -opt const
    $pie                      # -> 3.14159265358979
Arrays
    $a = @('a', 'b')          # Array    (round brackets, colons as separators)
    $a += 'c'                 # Push 'c'
    $a += 'd'                 # Push 'd'
    $a                        #  --> a\n b\n c\n d  (vertical stack)
    $a.GetType()              #  --> True     True    Object[]      System.Array

    $arry = 1,2,3,4,5         # Array, $arry[0] is 1, $arry[$arry.length-1] is 5.
    $arry.GetType()           # --> True     True     Object[]      System.Array
Associative arrays (hash tables)
    $assoc = @{one=1 ; two=2} # Associative array (curly brackets, semicolons as separators)
    $assoc.one                # -> 1
    $assoc['two']             # -> 2
    $assoc['three'] = 3       # Adds a member
    $assoc.four = 4           # Adds a member
    $assoc.GetType()          # --> True     True     Hashtable     System.Object
    $assoc                    #
The last statement ($assoc) gives:
       Name                           Value
       ----                           -----
       four                           4
       one                            1
       three                          3
       two                            2
Type cast a hash table as a custom object:
    $obj = [PSCustomObject]@{1 = 'one'; 2 = 'two'}
    $obj.1                    # --> one (accesses member of object with dot)
    $obj | fl *
Last statement gives:
       1 : one
       2 : two

Strings are as in PHP. 'Singly' quoted strings: no expansion of variables or escape character (backtick). "Doubly" quoted: expansion of variables. Furthermore, expressions under $ are evaluated. For example, "$(3*5)" gives 15, while "3*5" gives 3*5. Backtick escapes under double quotes "`$(3*5)" gives $(3*5). Backticks are unchanged under single quotes: '`$(3*5)' gives '`$(3*5)'. Note: "`n" gives the newline character. The string "John Doe" can be appended to file simply by "John Doe" >> out.txt. Note, however, that the file will be in UTF-16. The cmdlet add-content (alias ac) writes to file by default in ANSI (Windows-1252), and allows specification of other encodings.
Here-strings: \n@'\n ... \n'@\n (no expansion) and \n@"\n ... \n"@\n (with expansion). The newline symbol \n is not entered by the user, but indicates that @' and @" must start in column 1 and be on a single line. The same holds for '@ and "@.
Example, assume $a -eq "Big Brother":
@'
    $a
    $(3*5)
'@
gives
       $a
       $(3*5)
while
@"
    $a
    $(3*5)
"@
gives
       Big Brother
       15

12Comparison

Comparison operators are among others: -eq, -ne, -gt, -ge, -lt, -le, -like, -notlike, -match, -notmatch, and -cmatch. Although the operator -replace does not perform a comparison, it is usually included in this group.
Examples:
    'peanutbutter' -like 'nut'         # false
    'peanutbutter' -like '*nut*'       # true   (* is wildcard)
    'peanutbutter' -notlike '*nut*'    # false
    'peanutbutter' -notlike 'nut'      # true
    'peanutbutter' -match '[a-z]+'     # true   (regexp: all letters)
    'peanutbutter' -match 'r$'         # true   (regexp: last letter is r)
    'peanutbutter' -match '[A-Z]+'     # true   (PS is case insensitive)
    'peanutbutter' -cmatch '[A-Z]+'    # false  (cmatch  matches cases)
    'peanutbutter' -replace 'u', 'U'   # 'peanUtbUtter'

13Rename

The cmdlet rename-item (aliases: ren and rni) renames files, directories, and registry keys. In contrast to the cmd command rename, it does not allow wildcard in the name of the files. Although
    ren report.txt report.doc
is correct in cmd-mode as well as in PowerShell, the command that includes the wildcard *
    ren *.txt *.doc     # Error in PS!
only works in cmd-mode. PowerShell returns an error.
To explain how to rename more than one file in PS by a single command, we first observe that the cmdlet rename-item can take piped input for the original (old) name and takes as the new name the name defined by the flag -newname, for example,
    ls report.txt | ren -newname report.doc
gives the required change of the file extension.
To change multiple names the -replace operator may be used. Its syntax is:
     string -replace regexp, new_name
In string every substring that matches the regular expression regexp is replaced by new_name. For example,
     'report.txt' -replace '\.txt$', '.doc'  # -> 'report.doc'
The regular expression '\.txt$' is anchored at the end of the string by "$" and the dot is escaped so that its meaning is not the regexp arbitrary character but the ordinary punctuation mark. Thus,
    ls *.txt | ren -new { $_.name -replace '\.txt$','.doc' }
replaces in the current directory all file extensions txt to doc. The value of the script block is a string: the new file name.

14Switch

The following switch statement is case sensitive because of the flag -casesensitive:
    function f ($str) {
        Switch -casesensitive ($str) {
            'aap'  { write-host 'AAP'  }
            'noot' { write-host 'NOOT' }
            'mies' { write-host 'MIES' }
            'wim'  { write-host 'WIM'  }
            Default { "Unable to determine value of $str" }
        }
        "Statement after switch"
    }
    f('noot')    # --> NOOT \n Statement after switch
    f('Noot')    # Unable to determine value of Noot \n Statement after switch

15Builtin classes

PowerShell has built-in classes, one is [console] with methods (among others) beep and write: see the msdn (microsoft developer network) site.
    [console]::beep(800, 1000)    # beep at 800 Hz for 1000 msec
    [console]::write([char]0x07)  # Write hex 07, that is, ring the bell (does not work under ISE)
    [console]::readkey()          # Return name of key + modifier(not under ISE)
Another built-in class is [math]. See msdn.
Examples:
    [math]::pi                    # 3.14159265358979
    [math]::cos([math]::pi)       # -1
    [math]::max(-1,  -4)          # -1
    [math]::pow(10,3)             # 1000

16Random numbers

    Get-Random -min 0.0 -max 1.0  # Random nr between 0.0 and 1.0, see help Get-Random for bounds
Alternatively, use the System.Random object:
   $rand = New-Object Random
   $rand | gm                # Gives methods of $rand, among which NextBytes

The creation of a byte array by type casting is equivalent to the creation of a System.Byte object (a byte array of length 4 plus methods):
    [byte[]]$out = @(0,0,0,0) <-->  $out = New-Object Byte[] 4
Fill the array by a method of instance $rand:
    $rand.NextBytes($out)    # Fill array $out with 4 integer random numbers n: 0 -le n -le 255
    $out                     # To console

17Errors

Almost all cmdlets recognize the flag -ErrorAction, abbreviated: -ea. The parameters of this flag (Continue, etc) may be replaced by numbers, as follows:
    # -ErrorAction Continue | Ignore | Inquire | SilentlyContinue | Stop
    # -ea            2      |    4   |    3    |       0          |   1
For instance, suppress error message about inaccessible subdirectories as follows:
    ls -rec -ea 0 *.jpg  # all .jpg files in present and all subdirectories
As in many languages errors may be trapped. Enter Get-Help about_trap to see how. The very same info as web page is here: About trap.
Example of trapping:
    Trap [System.Exception] {
       "Command error trapped.`n$_" # Automatic variable '$_' contains system error msg; `n gives newline.
       continue                     # Suppress traceback, continue after erroneous statement
    }
    nonsenseString                  # Erroneous statement: unknown cmdlet, function or script.
    'Execution continues'
PS also has a Try ... Catch construct:
    try {
        An error                    # Illegal statement
    }
    catch {
       "An error occurred"
    }
The global object $error is a stack containing the consecutive non-trapped errors. To list the latest and the first error, respectively:
    $error[0] | fl                   # The latest
    $error[$error.count-1] | fl      # The first

18The formatting of strings

The PS formatting of strings is derived from the composite formatting of C#. The format operator is -f. Schematically it is used like:
     "String containing format-items"  -f elements to be formatted
Examples:
    "{0, 0:f4} is rounded to 4 decimals" -f 12.34567  # -> 12.3457 is rounded to 4 decimals
    "12345 converted to hex is: {0 :x}"  -f 12345     # -> 12345 converted to hex is: 3039
    "Prices are {0, 0:c} and {1, 4:c0}"  -f 12.3998, 1.99  # -> Prices are $ 12.40 and  $ 2
On the left-hand-side of the operator -f there is a string containing format-items:
    {index[,alignment]:[formatString]}
[Square brackets surround optional values]. The index is 0-based and refers to the position in the array on the right-hand-side of -f. The number of digits before the decimal point is adapted by the system so as not to lose information. The number of decimals (digits after the decimal point) is by default 2.

A composite-formatted string can be written by Write-Host:
    $str = "One decimal:{1,5:n1}; two decimals:{0, 7:n2}; three decimals:{2, 10:n3}"
    Write-Host($str -f 2.141, 1.141, 3.141)
Note the order: second argument first, then the first, and finally the third. Also look at the spacings dictated by the alignment parameter:
     One decimal:  1.1; two decimals:   2.14; three decimals:     3.141
                 |||||               |||||||                 ||||||||||
The following is an (incomplete) list of format strings, optionally they may be appended by an integer giving the number of decimals:
:c
Currency (symbol depends on the region set in Windows Control Panel: $, €, ...)
:d
Decimal. Only for integers (no decimals)
:e
Scientific (exp) notation
:f
Fixed point, n:fd gives n gives field of length n and d digits after the decimal point. If n is too small the system adapts.
:g
Most compact generic format, fixed or scientific
:n
Number, includes decimal point and thousands separators (actual symbols depend on the region set in Windows Control Panel))
:p
Percentage
:x
Hexadecimal format (only integers)

To get the sizes of all PS-scripts in the current directory (gp = Get-ItemProperty):
    gp *.ps1 |% length  <--> gp *.ps1 |% {$_.length}
The second form allows for computation (1kb is a literal constant of value 1024):
    gp *.ps1 |% {$_.length/1kb}     # Sizes in kilobyte
Prettify the format (round brackets are needed to give priority to the division):
    gp *.ps1 |% {"{0,10:f2}" -f ($_.length/1kb)}

19Functions

PS function is written in the PowerShell script language and is not compiled but interpreted. Often functions are written by end-users. In contrast, a cmdlet is written in a .net programming language such as C# ("C sharp") and is an intrinsic part of PS. A function name, just like a cmdlet name, is preferably of the form "verb-noun" where "verb" is any of the existing verbs.
To get the unique verbs of all (including user) functions sorted in alphabetical order:
   gcm -commandtype function |select  verb -unique |sort verb|ft
Example of a user function:
   function Write-ToScreen($path, $name) {
        Write-Host $path, $name
   }
Alternatively, the parameters can be defined by the param statement:
   function Write-ToScreen {
        param($path, $name)
        Write-Host $path, $name
   }
Both forms can be called as (parameters can be abbreviated to unique strings):
   Write-ToScreen -path roaming -name debug.txt    # --> roaming \n debug.txt
Or more briefly:
   Write-ToScreen roaming debug.txt        # Quotes are not required
Or more classically:
   Write-ToScreen('roaming', 'debug.txt')  # Quotes are required

With regard to return values: (most) console output generated in the body of the function is collected into an array which is returned. After completion of the function call, the return array may be written to the console (the default), or it may be assigned to a variable, or redirected to a file, or piped to a cmdlet. Indeed, the following three kinds of console output are collected in a return array:
  1. Write-Output.
  2. A simple string reference.
  3. The end of a pipeline (including a pipeline of one segment)..
Example:
   function list-no{
      write-output "first"
      "second"
      "third" |fl
   }
   $a = list-no     # Nothing to the console; output collected in return array $a
   $a               # --> first \nsecond \nthird  (vertically stacked)
Not all console output is collected: the cmdlets Write-Host and Out-Host -i write only to the console (do not generate return values):
   function list-yes{
      Write-Host "first"
      Out-Host -i "second"
   }
   $a = list-yes    # --> first \nsecond  (vertically stacked) to console
   $a               # No output
Because the return array may be redirected to a file and not all console output generated during function execution ends up in this array, the return values require close inspection. Example:
   function Write-Vars {
      $a = 'A';  $b = 'B'; $c = 'C'; $d = 'D'; $e = 'E';
      Write-Output $a           # To return array (including EOL chars)
      $e | fl                   # To return array
      Write-Host $b             # To console
      $c                        # To return array
      Out-Host -i $d            # To console
   }
   $f = Write-Vars      # 'B', 'D' to console; 'A', 'E', 'C' to $f
   $f                   # 'A', 'E', 'C' to console
   Write-Vars           # 'A', 'E', 'B', 'C', 'D' to console  (in order of assignment).
   Write-Vars > foo.out # 'B', 'D' to console; 'A', 'E', 'C' to foo.out
One could expect that the statement Write-Vars would write first the immediate values of $b and $d followed by the values of the return array, but this is not the case.
More examples:
   function list-txt{ls *.txt}
   $a = list-txt  # No console output, output of ls returned.
   $a             # Info of .txt files to console
   list-txt  | ft name  # Only file names to console

   function list-txt{ls *.txt|write-host}
   $a = list-txt  # Long file names of .txt files to console, nothing to $a.
   $a             # No output, no returned parameters.
Function parameters can have a default value:
   function f  {
       param($c = "Pete")
       $c
   }
   f         # -> Pete
   f John    # -> John
Positional parameters are passed in the automatic array $args:
   function Get-Pos {
       foreach ($p in $args) {
           Write-Host $p
       }
   }
   Get-Pos 1, 2, 'pink elephant',  icecream      # --> 1 2 pink elephant icecream
Functions may handle piped input (property names must be known—as always in pipelines). The named members of an object or hash table piped into the function are handled in a Process block:
   Function Test-PipedValue {
       Process {
           Write-Host "name:  " $_.name,
                      "color: " $_.color
       }
   }
Example, create hash table and pipe into function:
   $hash = @{name = 'Jean'; color = 'White'}
   $hash | Test-PipedValue   # --> name:   Jean color:  White
The process command loops over elements of the piped object:
   function Test-Loop{
       process {
          Write $_    # alias of write-output, appends EOL char
       }
   }
   1, 2, 3, 4 | Test-Loop # --> 1\n 2\n 3\n \4
See help about_functions for more info.

20Scripts

A script is a collection of PowerShell commands contained in a file with extension .ps1. The script is invoked from a PS session by entering its file name (= script name) prefixed by its path. If the script has parameters (defined in a param statement) their values follow the script name, optionally prefixed by the parameter names, just as is the case in a function invocation.
The output of scripts is comparable to that of functions. That is, the command Write-Host writes immediately (and only) to the console, while Write-Output writes to a return array. After termination of the script it is decided what happens to the return array: redirected to a file or to the console. Create the script Measure-Text.ps1 containing the following 8 lines:
   # Begin Measure-Text.ps1
      Param ([string]$filename)                              # Script with one parameter, a string.
      Write-Output "`nStatistics of $filename `:"            # To return array
      cat $filename | measure -line -word -character|ft      # To return array, counts of lines, words, and chars
      Write-Host "You will hear a beep after 2 sec:"         # To console
      sleep -sec 2
      Write-Host 'Beep'; [console]::beep(800,1000);          # To console
   # End Measure-Text.ps1
Compare what is written on the console when the output is redirected (the script measures its own length):
   .\Measure-Txt.ps1 -filename .\Measure-Txt.ps1 > foo.out
   cat foo.out
to when the script is called directly (parameter name -filename is optional and omitted):
   .\Measure-Txt.ps1 .\Measure-Txt.ps1
Variables and functions have a default scope which may be modified. For example, inside a script the following function has only the script as scope. The scope of the variable $a is modified to global and hence $a is known to the invoking PS session:
   function Display-Hello {
      "Hello, World"
      $global:a = 13
   }
   Display-Hello
   $a

21Traversing the Windows Registry

PowerShell is eminently suitable for traversing the Windows Registry. The Registry stores information needed by the programs installed in a Windows environment. It contains the following "hives";
  • hkey_classes_root (hkcr)
  • hkey_current_user (hkcu)
  • hkey_local_machine (hklm)
  • hkey_users (hku)
  • hkey_current_config (hkcc)
Each hive is a tree consisting of keys and subkeys that define traversable paths. Unless a subkey is the end of a path, it contains one or more subkeys that point further down their paths. A subkey may also contain value entries, which are name-value pairs that offer the desired information to installed programs. The value entries are the very reason for the existence of the Registry. In fact, the tree structure is only a means to help locate the value entries.

The only Registry hives predefined as PSDrives are HKCU and HKLM. The hives HKCR, HKU, and HKCC are not directly accessible by cd. One must issue cd registry::hkcr to access HKCR:, etc. The latter change of directory leads to the very long prompt:
   PS Microsoft.PowerShell.Core\Registry::HKCR>
Alternatively, one can define a new PSdrive by a command like:
   New-PSDrive -PSProvider registry -Root HKEY_CLASSES_ROOT -Name HKCR
followed by cd HKCR:. The advantage is the much shorter prompt string: PS HKCR:\>.

A Registry key that does not contain value entries is called empty. An empty key always contains one or more subkeys. An end node of a path is never empty, it always contains value entries, but by definition no subkeys. Clearly, value entries are not part of a path.

For use in the following examples, we set once and for all:
   set ps 'PSChildName' -opt constant
so that from hereon $ps -eq 'PSChildName'. In the examples below the current PSdrive is HKCU\software. One gets there in a PowerShell session by issuing
   cd hkcu:\software
Recall that -ea 4 stands for -ErrorAction Ignore. This flag suppresses errors about non-accessible keys of which there are many in the Registry. The alias gp stands for Get-ItemProperty. The alias gi stands for Get-Item. The functionality of gi overlaps to a large extent with ls and gp.
Now follows a list of examples that may be useful in inspecting/traversing the Registry:
   ls -rec -depth 1  -name    # Tabulate names of subkeys (children) and subsubkeys (grandchildren).
   gp *                       # Lists all value entries (name and value) in all subkeys plus a few PS variables that define
                              # three generations of the current path. Skips empty subkeys.
   ls 7-zip | gp              # Value entries (names and values) of the subkeys of 7-zip (plus a few PS variables).
   gp .                       # Lists value entries in present key. No output if present key is empty.
   gp * |ft $ps               # $ps='PSChildname' is a PS variable, returned by gp, that contains the name of subkey (child).
                              # Hence this tabulates names of all non-empty subkeys.
   gp microsoft               # No output because no value entry present in subkey 'microsoft' (is empty).
   gp RegisteredApplications  # Value entries (names and values) of hkcu:\software\RegisteredApplications + PS info
   gi *                       # Same as ls *; lists names of subkeys (also empty ones) and their value entries;
                              # no output when present key is endnode.
   gi .                       # Value entries of present key. Almost same info as 'gp .', but somewhat different format.
   gi .\Microsoft\Notepad     # Value entries of subkey
   gi .\microsoft\Notepad |fl *   # A subkey is an object, list its property names and values. One member is array 'Property'
   gi .\microsoft\Notepad |select -exp property # The content of array 'Property' in expanded form,
                                                # the array elements contain the names of value entries of the present key.
Note on ls * −rec −depth 1
Consider the following two commands issued from HKCU:\software:
   ls * -rec -depth 1 -ea 4 | measure -line   # gives 35329 lines
   ls   -rec -depth 1 -ea 4 | measure -line   # gives   804 lines
while both commands—issued from c:\—give the very same number of lines (542). It is difficult to see this dependence on context as anything but a bug. It is, therefore, advisable to never use ls * together with the flags -rec -depth.
End note

The following command lists names of subkeys and subsubkeys of the present key together with an array containing the names of their value entries:
   ls -rec -depth 1 |select name, property
Here property contains the names of all value entries, but only the first few elements are listed. To expand this array, together with the names of the subkeys separated by an empty line, use the following:
   ls -rec -depth 1 |select name, property |% {$_.name; $_.property; "" }
Explanation: ls returns an array of subkey objects that all have the properties name and property. The cmdlet select picks from each subkey object these two properties and adds them to an object that is referred to by $_ in the next stage of the pipeline. These objects are collected in an array that is passed to % = Foreach-Object. Then % loops over the array elements. The script block in the body of the loop simply issues the two member names as commands. Issuing of a variable name gives the writing of the content of the variable. If the content is an array, the array is written element for element, every element on a new line.

22Registry lookup

If a value entry or a subkey must be located, it is necessary to descend down hive trees. The cmdlet Set-Location (alias cd) enables this. The -recurse parameter of ls does not imply any cd in a script block. An explicit cd is necessary if some processing must be performed lower down the path. For example, recalling that ls -name -rec -de 1 returns an array containing names of subkeys and subsubkeys, we see that the following statement gives the names of all subkeys and subsubkeys by execution of pwd (which returns the name of the present key), including those without value entries (the empty ones):
   ls -name -rec -depth 1 |% {$p=pwd; cd $_ -ea 4; pwd; cd $p; rv p}
Here -ea 4 suppresses the listing of an error when a cd to a non-accessible subkey is attempted. The newly created variable $p is removed to avoid possible later side effects. Compare this statement to the following command that lists names (contained in PSPath) of only non-empty subkeys and subsubkeys, but does not require a cd:
   ls -name -rec -depth 1 |gp |select pspath
Once one has descended to a certain key, the names of its value entries (if any) are obtained by:
   gi . |select -exp property  # This returns line by line the names of the value entries.
The command gpv -name entry_name returns the value coupled to entry_name. For instance, the subkey 7-zip of HKCU\software contains the value entry pair (lang: nl), i.e., it has entry name lang and entry value nl. The commands issued from HKCU\software:
    $p=pwd; cd 7-zip; gpv -name lang; cd $p; rv p;
effectively leave us in HKCU\software and outputs the entry value nl named lang.

To list the value entries (if present) of the present key, use
   gi . |select -exp property |% {$v=gpv -name $_; write-host $_":", $v; rv v;}
The next command lists names ($p) of non-empty subkeys, subsubkeys, and subsubsubkeys, name of value entry ($name) and corresponding value ($v). It does not list the value entries of the current key. Note the nesting of |% and the line continuation:
   ls -rec -name -de 2 |% {$cd=pwd; $p=$_; cd $p -ea 4; gi . |select -exp property|`
   % {$name=$_; $v=gpv -name $name; write-host $p": ", $name" = "$v; rv name, v }; cd $cd; rv cd, p}
The final command finds entry values containing a given string in keys below the current key. It is important to note that the search time increases exponentially with the value of the parameter -depth:
   $string = "aul"
   ls -rec -name -depth 2 |% {$cd=pwd; $p=$_; cd $p -ea 4; gi . |select -exp property|`
   % {$name=$_; $v=gpv -name $name; if ($v -match $string) {write-host $p": ", $name" = "$v;}; rv name, v }; cd $cd; rv cd, p}
   rv string