At work we are doing some migration work and the vendor we are migrating data to has a REST API which we can talk to using curl and pass data as JSON. I spent the last two weeks creating various bash scripts that can send and receive JSON and while I did a good job (in my opinion), and learnt a lot of things (discovered jq for instance, it’s amazing!), and it was a great working with bash and sed and awk and all these *nix tools after such a long time (and all this was done on the macOS this time, so was a good way to send time on the macOS CLI too) … I now realize that doh PowerShell supports JSON and I could have used Invoke-WebRequest for all my curl calls, so I could have done the whole work in PowerShell … a much more familiar environment! In the process I could have saved some time and taken a lot less stress.

That’s why I am bummed. I am proud I did a good, but I also kind of wish I had been more aware of what PowerShell can do and taken the effort to Google a bit about it.

Thing is I have a huge soft corner for bash and all these things, so I know it’s my internal bias that just pushed me to jump at the opportunity and work with this rather than check out PowerShell. I do love me bash and sed and all those. :)

Spent a lot of time today with this. Such an irritating topic! Primarily coz there doesn’t seem to be much info on how to do this correctly.

I have blogged about this in the past, in an opposite that. That time once I connect via VPN my macOS wasn’t picking up the DNS servers offered by VPN. That was a different VPN solution – the Azure Point to Site VPN client. Since then I have moved to using GlobalProtect and that works differently in that it over-rides the default resolvers and makes the VPN provided ones the primary.

Here was my DNS configuration before connecting to VPN:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

/Users/rakhesh$scutil--dns

DNS configuration

resolver#1

nameserver[0]:2001:8f8:172d:3ca9::1

nameserver[1]:192.168.17.1

if_index:10(en0)

flags:RequestArecords,Request AAAA records

reach:0x00020002(Reachable,Directly Reachable Address)

<snip>

DNS configuration(forscoped queries)

resolver#1

nameserver[0]:2001:8f8:172d:3ca9::1

nameserver[1]:192.168.17.1

if_index:10(en0)

flags:Scoped,RequestArecords,Request AAAA records

reach:0x00020002(Reachable,Directly Reachable Address)

Once I connect via GlobalProtect these change:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

/Users/rakhesh$scutil--dns

DNS configuration

resolver#1

search domain[0]:workdomain.com

nameserver[0]:10.222.5.21

nameserver[1]:10.222.5.22

flags:RequestArecords,Request AAAA records

reach:0x00000002(Reachable)

order:50000

<snip>

DNS configuration(forscoped queries)

resolver#1

nameserver[0]:2001:8f8:172d:3ca9::1

nameserver[1]:192.168.17.1

if_index:10(en0)

flags:Scoped,RequestArecords,Request AAAA records

reach:0x00020002(Reachable,Directly Reachable Address)

And I have no idea how to change the order so that my home router stays at #1 and the VPN provided one is added as #2.

Initially I thought the “order” in the DNS configuration might play a role. So I tried something changing the order of my home router to be better than the VPN one. I tried both setting it to a larger and smaller number, neither worked.

As an aside, don’t use tools like ping or nslookup to find how your DNS resolution is working. From this StackOverflow article which I’d like to copy paste here:

macOS has a sophisticated system for DNS request routing (“scoped queries”) in order to handle cases like VPN, where you might want requests for your work’s domain name to go down your VPN tunnel so that you get answers from your work’s internal DNS servers, which may have more/different information than your work’s external DNS servers.

To see all the DNS servers macOS is using, and how the query scoping is set up, use: scutil --dns

DNS-troubleshooting tools such as nslookup(1), dig(1), and host(1) contain their own DNS resolver code and don’t make use of the system’s DNS query APIs, so they don’t get the system behavior. If you don’t specify which DNS server for them to use, they will probably just use one of the ones listed in /etc/resolv.conf, which is auto-generated and only contains the default DNS servers for unscoped queries.

Traditional Unix command-line tools that aren’t specific to DNS, such as ping(8), probably call the traditional gethostbyname(3) APIs, which, on macOS, make use of the system’s DNS resolver behaviors.

To see what your DHCP server told your Mac to use, look at the domain_name_server line in the output of: ipconfig getpacket en0

So ping is probably fine but nslookup and dig are definitely a no-no.

Anyways, in my case I finally decided to do remove the DNS entries provided by VPN altogether and replace it with my home router DNS. I’d have to do this each time I reconnect to VPN, but that can’t be helped I guess. If I launch scutil from the command line and look at the list of services and their DNS settings I can identify the one used by GlobalProtect.

I just chose to over-ride both the SearchDomain and ServerAddresses with my local settings (thanks to this post and this):

1

2

3

4

5

>d.remove SearchDomains

>d.remove ServerAddress

>d.add ServerAddresses*192.168.17.12001:8f8:172d:3ca9::1

>setState:/Network/Service/gpd.pan/DNS

>exit

For my own copy paste convenience for next time, here’s what I would have to do once I launch scutil:

1

2

3

4

5

6

get State:/Network/Service/gpd.pan/DNS

d.remove SearchDomains

d.remove ServerAddress

d.add ServerAddresses*192.168.17.12001:8f8:172d:3ca9::1

setState:/Network/Service/gpd.pan/DNS

exit

Ok, so far so good. But what if I also want resolution to the VPN domains working via VPN DNS servers when I am connected? Here I go back to what I did in my previous blog post. I create multiple files under /etc/resolver for scoped queries, each having different search_order (queries to internal DNS have a lower search_order and a timeout; queries to external DNS have a higher search_order).

Update: Turns out GlobalProtect over-writes the DNS settings periodically. So I made a script as below in my home directory:

I know what to do when it comes to finding a list of available modules in PowerShell (Get-Module -ListAvailable) and a list of cmdlets in each module (Get-Command -Module <name>). But I don’t use snap-ins much and so the equivalent for that isn’t always available on my fingertips. So this blog post is a reminder to future myself:

To list all registered snap-ins:

1

Get-PSSnapin-Registered

And to find the cmdlets in a particular snap-in:

1

Get-Command|?{$_.PSSnapin.Name-match"<Name>"}

Of course replace -match with -eq if you know the snap-in’s exact name.

Update: I am not sure (coz I don’t recollect my steps) but maybe I need to do an Add-PSSnapin <name> first before Get-Command.

I had to change option 150 (Cisco VoIP) for multiple scopes across multiple servers as we are moving our Call Manager to a new location this weekend, and couldn’t be bothered doing it manually post move.

All my voice scopes have the word Voice or VoIP in them so I match on them. A lot of the code is verbose coz I like to be a bit verbose, but you could probably crunch it all down to a single line or two. :)

Here’s something I had to do at work a few weeks back. I wanted to blog about it since then but never got around to it.

We had copied a bunch of folders from one location to another. Since this was a copy the folders lost their original ACLs. I wanted to do two things – 1) the folder names were in the format “LastName, FirstName” and I wanted to change that to “username” (I had a CSV file with mappings so I could use that to do the renaming). 2) I wanted to change the ACLs so the user had modify rights to the folders.

For the first task here’s what I did:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

$Mappings=@{}

foreach($rin(Import-Csv\path\to\Mappings.csv))

{

$Mappings[$r.Olduser]=$r.NewUser

}

foreach($folderin"G:\Folder1","G:\Folder2"){

Get-ChildItem$folder-Directory|%{

$oldPath=$_.FullName;

$oldUser=Split-Path-Leaf$oldPath;

$newUser=$Mappings[$oldUser];

if($newUser-ne$null){

$newPath="G:\NewFolder\$newUser"

# Write-Host "Will move from $oldPath to $newPath"

Move-Item$oldPath$newPath

}

}

}

Note that apart from renaming I also move the folder to a different path (coz I had multiple source locations and wanted to combine them all into one).

Yeah, I cheated in the end by using > rather than Out-File. Old habits. :)

Not sure why, but doing a search like Get-GPO -All | ?{ ($_.ModificationTime) -eq [datetime]"18 September 2017" } doesn’t yield any results. I think it’s because the timestamps don’t match. I couldn’t think of a way around that.

Was reading about the WannaCrypt attacks. If you have the MS17-010 bulletin patches installed in your estate, you are safe. I wanted to quickly scan our estate to see if the servers are patches with this. Not my job really, but I wanted to do it anyways.

The security bulletin page lists the actual patch numbers for each version of Windows. We only have Server 2008 – 2016 so that’s all I was interested in.

Here’s a list of the Server name, internal version, and the patch they should have.

Server 2008 | 6.0.6002 | KB4012598

Server 2008 R2 | 6.1.7600 | KB4012215 or KB4012212

Server 2012 | 6.2.9200 | KB4012214 or KB4012217

Server 2012 R2 | 6.3.9600 | KB4012213 or KB4012216

Server 2016 | 10.0.14393 | KB4013429

One thing to bear in mind is that it’s possible a server doesn’t have the exact patch installed, but is still not at any risk. That is because since October 2016 Windows patches are cumulative. So if you don’t have the particular March 2017 patch installed, but do have the April 2017 one, you are good to go. The numbers above are from March 2017 – so you will have to update them with patch numbers of subsequent months too to be thorough.

Another thing – I had one server in my entire estate where the patch above was actually installed but turned up as a false positive in my script. Not sure why. I know it isn’t a script issue. For some reason that patch wasn’t being returned as part of the “Win32_QuickFixEngineering” output. Am assuming it wasn’t installed that way on this particular server.

Patch Tuesday is upon us. Our pilot group of server was patched via SCCM but there were reports that 2012R2 servers were not picking up one of the patches. I wanted to quickly identify the servers that were missing patches.

The first two lines basically enumerate the two groups. If it was just one group I could have replaced it with Get-ADGroupMember "GroupName".

The remaining code checks whether the server is online, filters out 2012 R2 servers (version number 6.3.9600), and makes a list of the servers along with the installed date of the hotfix I am interested in. If the hotfix is not installed, the date will be blank. Simple.

Oh, and I wanted to get the output as and when it comes so I went with a Width=20 in the name field. I could have avoided that and gone for an -AutoSize but that would mean I’ll have to patiently wait for PowerShell to generate the entire output and then Format-Table to do an autosize.

Update: While on the Win32_QuickFixEngineering WMI class I wanted to point out to these posts: [1], [2]

Worth keeping in mind that Win32_QuickFixEngineering (or QFE for short) only returns patches installed via the CBS (Component Based Servicing) – which is what Windows Updates do anyway. What this means, however, is that it does not return patches installed via an MSI/ MSP/ MSU.

Our HP Gen8 ESXi hosts were randomly crashing ever since we applied the latest ESXi 5.5 updates to them in December. Logged a call with HP and turns out until a proper fix is issued by VMware/ HPE we need to change a setting on all our hosts and reboot them. I didn’t want to do it manually, so I used PowerCLI to do it en masse.

I could have done the reboot along with this, but I didn’t want to. Instead I copy pasted the list of affected hosts into a text file (called ESXReboot.txt in the script below) and wrote another script to put them into maintenance mode and reboot one by one.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

Get-Content.\ESXReboot.txt|%{

$Server=$_;

Write-Host-ForegroundColorGreen"$Server"

Write-Host-ForegroundColorYellow"`tEntering maintenance mode"

Set-VMHost$Server-Statemaintenance-Evacuate|Out-Null

Write-Host-ForegroundColorYellow-NoNewline"`tRebooting"

Restart-VMHost$Server-Confirm:$false|Out-Null

do{

sleep15

$ServerState=(Get-VMHost$Server).ConnectionState

Write-Host-ForegroundColorYellow-NoNewline"."

}while($ServerState-ne"NotResponding")

Write-Host-ForegroundColorYellow-NoNewline"(down)"

do{

sleep30

$ServerState=(Get-VMHost$Server).ConnectionState

Write-Host-ForegroundColorYellow-NoNewline"`."

}while($ServerState-ne"Maintenance")

Write-Host-ForegroundColorYellow"(up)"

Write-Host-ForegroundColorYellow"`tExiting maintenance mode"

Set-VMHost$Server-StateConnected|Out-Null

Write-Host-ForegroundColorYellow"`tDone!"

Write-Host""

}

The screenshot output is slightly different from what you would get from the script as I modified it a bit since taking the screenshot. Functionality-wise there’s no change.

At work as part of some security certification we are running Nessus scans on all our systems and it came up with the following vulnerability – link. Read that link, it’s good info.

Basically if one of your Windows Service entries point to (say) “C:\Windows\Microsoft.NET\Framework64\v3.0\Windows Communication Foundation\SMSvcHost.exe” without the double quotes then one could potentially create a malicious file called Windows.exe at “C:\Windows\Microsoft.NET\Framework64\v3.0\Windows” and Windows will execute that file instead of parsing the full path and treating it as part of a folder name. That’s because Windows uses space as a delimiter between a command and its switches & arguments and so it could treat the entry as “C:\Windows\Microsoft.NET\Framework64\v3.0\Windows.exe” with arguments “Communication Foundation\SMSvcHost.exe“.

The solution for this is to find all such entries that contain a space, and if the path is not in double quotes then make it so. You have to do this in the registry, so you could either do it manually or make a script and do it en masse. I went the latter route so here’s something I created.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

param(

[switch]$FixIt

)

# Quick and dirty script by Rakhesh to fix unquoted path vulnerabilities in service paths

# This code is licensed under the MIT license - https://opensource.org/licenses/MIT

# https://isc.sans.edu/diary/Help+eliminate+unquoted+path+vulnerabilities/14464 for more info on the vulnerability itself - good read!

Get-Content.\ServerNames.txt|%{

# Preliminary stuff for registry access

$Type_SZ=[Microsoft.Win32.RegistryValueKind]::String

$Hive=[Microsoft.Win32.RegistryHive]::LocalMachine

$KeyPath="SYSTEM\CurrentControlSet\services"

$ComputerName=$_;

# hat tip to http://stackoverflow.com/a/19015125 for this newer way of creating a custom object

$ResultObj=[pscustomobject]@{

ComputerName=$ComputerName

Status="Online"

SubKey=$null

Original=$null

Replacement=$null

}

if(Test-Connection-ComputerName$ComputerName-Quiet-Count2){

# Clear the variable that will hold the registry connection

$Reg=$null;

# Open remote registry and if it fails then set the status accordingly

# Open the subkey read-only (else we get errors on some keys which we don't have write access to)

$Subkey=$Key.OpenSubKey($SubkeyName,$false)

# Get value of ImagePath

$Value=$Subkey.GetValue("ImagePath")

# Match ImagePath to see if it has an exe; if yes, extract the exe path. Note: extract only exe path, not arguments.

# If this extracted path doesn't start in quotes and when we split it for spaces we get more than one result, then enclose path in double quotes.

if($Value-match".*\.exe"){

if(($Matches[0]-notlike'"*')-and(($Matches[0]-split'\s').Count-gt1)){

$Replacement='"'+$Matches[0]+'"'

$NewValue=$Value-replace".*\.exe",$Replacement

$ResultObj.SubKey=Split-Path-Leaf$SubKey;

$ResultObj.Original=$Value;

$ResultObj.Replacement=$NewValue;

$ResultObj

if($FixIt){

# re-open the key with read-write permissions

$Subkey=$Key.OpenSubKey($SubkeyName,$true)

$Subkey.SetValue("ImagePath","$Replacement");

if($?){Write-Host-ForegroundColorGreen"Success!"}else{Write-Host-ForegroundColorRed"Something went wrong!"}

}

}

Clear-VariableMatches

}

}

}

}else{$ResultObj.Status="Online";$ResultObj}

}

I am being lazy and not really offering input etc as I just expect a list of servers to be scanned in a file called “ServerNames.txt”. I have the above saved to a .ps1 file and I simply run it as .\Registry.ps1. Feel free to adapt to your needs.

What it does is that it connects to the server specified (provided they are online), tries to open the “services” key under HKLM (assuming it has access), and then enumerates all the subkeys that contain the service names and checks if the path has a space. It only matches against paths containing a .exe so it could miss out some stuff. Once it finds a match it extracts the bit up to the .exe, splits it along any spaces, and if there are more than one results (which means the path did have spaces) it encloses it in double quotes and replaces the original entry.