Zipping your DSC Resources

When you are setting up a DSC Pull scenario, you will have to zip your DSC Resources in a correct format and by using proper tooling.

At first you need to zip your primary directory, and you have to give your zip file the correct naming. This should be Resourcename_versionnumber.zip (see screenshot below), and then placed in the “C:\Program Files\WindowsPowerShell\DscService\Modules directory”.PSDSC_Pull_Server

Please be aware that even though its ‘just a zip file’ not all programs zip the same way, if you for example use 7zip or winrar you could bump into eventid 4104: Failed to extract the module from zipfile.                                            PSDSC_notpropperlyzipped

If you however use the windows native compressing engine, everything will work out fine.PSDSC_zippedok

DSC Resource versioning fixed with WMF5 Production preview!

Last week I blogged about an issue with the DSC resource folder versioning in a DSC Pull scenario. DSC Resource folder versioning issue in a DSC-Pull scenario.

This week Microsoft released a new version of WMF 5.0  which fixes a whole range of issues.

While you still have to zip your DSC Resource without the version folder, the DSC engine now extracts the ZIP file to the corresponding folder version!

DSC Resource zip structure:

PSDSC_Pull_Server

DSC Resource client structure:

PSDSC_Pull_Client:

This means that now, we can use multiple versions of a DSC Resource on the same client. This will help when for example you only want to update 1 partial configuration on a server that uses a newer resource, while not having to update dependent others.

Disable privacy sensitive settings in Windows 10

Recently a minor discussion started with one of my Facebook friends, where people found the new data collecting engines from Microsoft a bit annoying. Personally I find that Windows 10 has alot more features and stability to offer, and that these settings should not be the reason for you to hold back on a better operating system. However I do understand some people’s concerns.

Example article:  http://lifehacker.com/what-windows-10s-privacy-nightmare-settings-actually-1722267229

This made me think, there should be a way to just quickly disable all those annoyances. Below you can find the result of a quick Posh script that takes care this for you.

DisablePrivacySettings_Win10.ps1

###########################################################################
## Created by Danny den Braver @29-08-2015
##
## I primary made this script due to a minor discussion that started on FB about the Windows 10 Privacy 'flaws'
## This script will disable most of the 'features' that microsoft uses to gain privacy sensitive data
##
## Currently this script disables Telemetry and DataCollection, Wifi Sense, SmartScreen Filter & Cortana
##########################################################################

#region Disable Telemetry and Data Collection

Write-Verbose -Message 'Disabling: Telemetry and Data Collection' -Verbose

# Create registry key to disable Telemetry
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" -Name AllowTelemetry -Value '0' -PropertyType DWord -Force | Out-Null 

# Disabling & Stopping service DiagTrack
Set-Service -name DiagTrack -StartupType Disabled
Stop-Service DiagTrack

# Disabling & Stopping App Push Service
Set-Service -name dmwappushservice -StartupType Disabled
Stop-Service dmwappushservice

#endregion

#region Disable Wifi-Sense (Wifi Sharing)

Write-Verbose -Message 'Disabling: Wifi-Sense (Wifi Sharing)' -Verbose

# Create Registry key into policy to disable Wi-fi Sense
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\WcmSvc\wifinetworkmanager\config" -Name AutoConnectAllowedOEM -Value '0' -PropertyType DWord -Force | Out-Null 

#endregion

#region Disable SmartScreen Filter

Write-Verbose -Message 'Disabling: SmartScreen Filter' -Verbose

# Create Registry key into policy to disable Microsoft SmartScreen
New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\System" -Name EnableSmartScreen -Value '0' -PropertyType DWord -Force | Out-Null
 
#endregion

#region Disable Cortana

# Read current user
$whoami = whoami
$domain = ($whoami).split("\")[0]
$username = ($whoami).split("\")[1]

if (Test-Path 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe')
{
    Write-Verbose -Message 'Disabling: Cortana' -Verbose
    # Change permissions on Cortana folder & executable
    $objUser = New-Object System.Security.Principal.NTAccount($domain, $username)
    $objFile = Get-Acl 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe'
    $objFile.SetOwner($objUser)
    Set-Acl -aclobject $objFile -path 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe'
    Set-Acl -aclobject $objFile -path 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy'
    icacls 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe' /grant "$($whoami):(OI)(CI)F"
    icacls 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy' /grant "$($whoami):(OI)(CI)F"

    # Rename Cortana Executable so it no longer starts
    Rename-Item 'C:\Windows\SystemApps\Microsoft.Windows.Cortana_cw5n1h2txyewy\SearchUI.exe' 'Disabled.SearchUI.exe'
}

else 
{
    Write-Verbose -Message 'Check: Cortana was already disabled' -Verbose
}

#endregion

Write-Warning -Message 'Please be aware that you will need to restart your computer for settings to take effect'

DSC Resource folder versioning issue in a DSC-Pull scenario.

Please be aware of the following if you are using DSC Pull with folder versioning in your DSC Resources

In my case, I have a custom DSC Resource named cDNSRecord with the following directory structure: .\cDnsServer\1.1.0.0\DSCResources\cDNSRecord.

It seems that currently as in WMF 5.0 (April 2015 Preview) it is not supported to have resources with folder versioning in a DSC Pull scenario and will most likely end you up with error 4104 in the event log:

ErrorId is 0x1. ErrorDetail is The PowerShell DSC resource % not contain the corresponding MOF file %.

EventID4252-1DSC

A Work-around to this solution is simple:unzip your resource, and remove the folder versioning to for example: .\cDnsServer\DSCResources\cDNSRecord

While you can now use your DSC Resource in a Pull scenario, this does mean that you cannot use multiple versions of a DSC Resource on 1 sever if you use DSC Pull. This also means that if you want to upgrade to a newer version for this DSC Resource you will first need to remove the old version from the C:\Program Files\WindowsPowerShell\Modules directory on this specific server.

If you forget to remove the old folder you will most end up with another errorid 4252 in the event log:

Error Message: Installation of module % failed since the module directory already exists at %.Message ID: ModuleDirectoryAlreadyExistsEventID4252-2DSC

This can be resolved by using the AllowModuleOverwrite = $true value in your LCM configuration.

Hopefully this issue will be resolved in a future release of WMF5.

 

Scan Cluster Volume Health

I created this function some time ago already when we did not have the SCOM Management Pack for Windows Clusters imported yet.

This script basically scans a Cluster, to scan its current CSV State, and report you the current size/usage/freespace available.

The output of this script can be used to filter out unhealthy volumes, or volumes that are filling up!

Function GetClusterVolumeState{
<#
.SYNOPSIS
Retrieve information about the Cluster Volume Health
.DESCRIPTION
The GetClusterVolumeState function retrieves information about the cluster volumes on a certain cluster.
This information will show the Cluster, Volume, Health and Disk space.
.PARAMETER Clusters
One or multiple clusters you would like to retrieve information from
.EXAMPLE
Retrieve information about the Clustervolumes for Cluster01
RI-GetClusterVolumeState -clusters "Cluster01"
.NOTES
This script was written by Danny den Braver @2013, for questions please contact danny@denbraver.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param([array][Parameter(Mandatory=$true)]$Clusters)

foreach ($Cluster in $Clusters){
    # Test Connection availability and continue if $true
    $testconnection = Test-Connection $Cluster -Quiet
    if ($testconnection -eq $true){
        Write-Verbose -Message "Scanning: $Cluster for clusterdisk information"
	    # Get CSV Volumes
	    $CSVDisks = Get-ClusterSharedVolume -Cluster $Cluster
	    foreach ($CSVDisk in $CSVDisks){
		    # Get CSV VolumeInformation
		    $CSVVolumeInfo = $CSVDisk | Select -Expand SharedVolumeInfo
		    $CSVFriendlyName = $CSVVolumeInfo.FriendlyVolumeName
		    $CSVVolume = ($CSVFriendlyName.Split("\")[2]).ToLower()
		    $CSVState = $CSVDisk.State
		    # Get CSV free space
		    [int]$CSVDiskDriveFreeSpace = ([math]::round((($CSVVolumeInfo | Select -Expand Partition).FreeSpace / 1GB), 0))
		    # Get CSV total space
		    [int]$CSVDiskSpace = ([math]::round((($CSVVolumeInfo | Select -Expand Partition).Size / 1GB), 0))
		    # Get CSV used Space
		    [int]$CSVDiskDriveUsedSpace = [int]$CSVDiskSpace - [int]$CSVDiskDriveFreeSpace
		    # Get CSV Health
		    if ($CSVState -eq "Online"){
			    if ($CSVVolumeInfo.MaintenanceMode -eq $False){
				    if ($CSVVolumeInfo.RedirectedAccess -eq $True){
					    $CSVHealth = "Warning"
					    $CSVHealthDetails = 'Volume is in redirected mode.'
				    }
				    else {
					    $CSVHealth = "Healthy"
					    $CSVHealthDetails = 'Volume is healthy.'
				    }
			    }
			    else {
				    $CSVHealth = "Maintenance"
				    $CSVHealthDetails = 'Volume is in maintenance mode."'	
			    }									
		    }
		    else {
			    $CSVHealth = "Critical"
			    $CSVHealthDetails = 'Volume is in offline state.'
		    }
 
		    # CSV Output
            $clustername = $cluster -replace ".eu.rabonet.com",""
            $clustername | select   @{l="Clustername";e={$Clustername}},
                                    @{l="Volume"; e={"$CSVVolume"}},
                                    @{l="DiskSpaceGB"; e={"$CSVDiskSpace"}},
                                    @{l="UsedSpaceGB"; e={"$CSVDiskDriveUsedSpace"}},
                                    @{l="FreeSpaceGB"; e={"$CSVDiskDriveFreeSpace"}},
                                    @{l="Health"; e={"$CSVHealth"}},
                                    @{l="Health Description"; e={"$CSVHealthDetails"}}
	    }
    }
    else { Write-Warning "Could not connect to $Cluster" }
}
}

 

Make a SSH Connection using Powershell

Ever needed to connect to a hardware or unix interface from a windows box? For example to manage HP Servers / Interfaces, or to run some code on a Linux appliance?

Over at Powershelladmin.com they have an answer to this: SSH-Sessions powershell module.

Copy the SSH-Sessions folder to your C:\Windows\System32\WindowsPowerShell\v1.0\Modules directory and then unblock the SSH.Net library using Unblock-File C:\Windows\System32\WindowsPowerShell\v1.0\Modules\SSH-Sessions\Renci.SshNet.dll

After that you can just Import-module the SSH-Sessions and you’re on your way:

get-help *ssh*

Name                    Category  Synopsis
----                    --------  --------
Get-SshSession          Function  Shows all, or the specified, SSH sessions in the global $SshSessions var...
Remove-SshSession       Function  Removes opened SSH connections. Use the parameter -RemoveAll to remove a...
New-SshSession          Function  Creates SSH sessions to remote SSH-compatible hosts, such as Linux...
Invoke-SshCommand       Function  Invoke/run commands via SSH on target hosts to which you have already op...
Enter-SshSession        Function  Enter a primitive interactive SSH session against a target host....

Has your hotfix been installed?

Of course you may wonder, why do you not use SCCM or WSUS to check if your software was installed successfully. Sadly when working in an enterprise environment you do not always have access to all the tooling you require, even though you have administrator access to the servers you need to manage.

Recently I was asked if I could scan all our Hypervisors to see if the latest December update rollup was installed, and if possible to mail it to the team’s functional mailbox.

This resulted in the following simple script:

Function CheckHotfix {
<#
.SYNOPSIS
Checks if Microsoft hotfixes are installed against KB numbers (reports if missing).
.DESCRIPTION
The CheckHotfix will check the hosts against the list of KB numbers.
If a KB number is not found it will be reported back to you.
Please make use of the -verbose switch to see information of what the script is handling.
.PARAMETER Server 
A single Servername, or an array of multiple Servers, this field is mandatory.
.PARAMETER KBnumbers 
One or multiple KB numbers, this field is mandatory.
.EXAMPLE
Scan 1 server for a KB number
CheckHotfix -Server "Server01" -KBnumbers "292929"
.EXAMPLE
Scan multiple servers for FC Card information
$computernames = (Get-scvmhost).computername
$kbnumbers = "292929 - doesnt exist","KB3013769 - Update Rollup December 2014"
CheckHotfix -Server $computernames -KBnumbers $kbnumbers
.NOTES
You need to be local administrator on the Server(s) you are querying.
This script was written by Danny den Braver @2015, for questions please contact danny@denbraver.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param([array][Parameter(Mandatory=$true)]$Server,
      [array][Parameter(Mandatory=$true)]$KBnumbers)

ForEach ($Server_ in $Server){
    #Test Connection availability and continue if $true
    $testconnection = Test-Connection $Server_ -Quiet
    if ($testconnection -eq $true){
        Write-Verbose -Message "Scanning: KB Article for server $Server_"
        foreach ($kbnumber in $KBnumbers){
            $kba = $null
            $kbnumbersmall = ($kbnumber.split(" "))[0]
            $kba = Get-WmiObject -ComputerName $Server_ -query 'select * from win32_quickfixengineering' |  ? {$_.hotfixid -contains $kbnumbersmall}
            if ($kba -eq $null){
                $Server_ | select @{Name="Computername"; Expression={$Server_}},
                @{Name="Productionlevel"; Expression={(Get-WmiObject Win32_environment -computername $server_ | ? {$_.name -eq "Productionlevel"}).variablevalue}},
                @{Name="Not Installed"; Expression={$kbnumber}}
            }
        }
    }
    else {Write-Warning "Cannot connect to $Server_"}
}

}

Now you only need to add your results to an email message using “send-MailMessage”

SCOM Maintenance Mode

Have you ever had the hassle of needing to put multiple servers into SCOM maintenance mode, yet you dont have the Powershell commandlets installed, or you are using an older version of SCOM that just does not do what you would like it to do.

I ran into the same issue with a customer, that was using SCOM 2007R2, while at some days it would really come in handly to place servers into maintenance mode scripted.

The script below can be your answer to this problem. Simply use the function SCOMMaintenanceMode -servername “server” -maintenancemodeminutes “minutes” or even push an array of servers against it. It will first check if maintenance mode is already set and if the current duration is not longer then what you just told the script.

Function SCOMMaintenanceMode {
<#
.SYNOPSIS
Sets or Stops SCOM Maintenance Mode for one or multiple Computers/Servers.
.DESCRIPTION
The SCOMMaintenanceMode function uses the PowerShell Snapin Microsoft.EnterpriseManagement.OperationsManager.Client to read the current maintenance duration (if set). If Maintenance is not set fot a computer for a duration that lasts longer then the user defined setting, it will try to place it into SCOM Maintenance mode with the reason Planned (Other).
Please make use of the -verbose switch to see information of what the script is handling.
.PARAMETER Servername 
A single computer name, or an array of computernames, this field is mandatory.
.PARAMETER Comment
The comment that is giving to the SCOM Maintenance mode, by Default: "Maintenance Mode Job" is given.
.PARAMETER MaintenanceModeMinutes 
The number of minutes the server or servers will be placed into maintenance mode.
.PARAMETER StopMaintenancemode
A switch that is given to the script to stop maintenance mode, instead of setting it.
.EXAMPLE
Place 1 server into maintenance mode.
SCOMMaintenanceMode -servername "Server01" -MaintenanceModeMinutes "60"
.EXAMPLE
Place multiple servers into maintenance mode.
$computernames = "server1","server2","server3"
SCOMMaintenanceMode -servername $computernames -MaintenanceModeMinutes "60"
.EXAMPLE
Give your personal comment to the maintenance mode.
SCOMMaintenanceMode -servername "Server01" -Comment "Because I can" -MaintenanceModeMinutes "60"
.EXAMPLE
Stop Maintenance Mode.
SCOMMaintenanceMode -servername "Server01" -Stop
.NOTES
It is required to have access to the computer account in SCOM to be able to place this server into maintenance mode. In older versions of Microsoft Powershell© it may be possible to receive a warning/alert when a server is not placed in maintenance mode. This is related to a check to see if the current maintenance mode has been set for a longer period, and the try/catch filter not applying propperly.
This script was written by Danny den Braver @2014, for questions please contact danny@denbraver.com
#>
[CmdletBinding(SupportsShouldProcess=$true)]

param( [array][Parameter(Mandatory=$true)]$Servername,
       [string]$Comment="Maintenance Mode Job",
       [int]$MaintenanceModeMinutes=60,
       [switch]$Stop)

#Load Snapin
Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client

foreach ($server_ in $servername){

    $computerPrincipalName=(Get-ADComputer $server_).dnshostname
    $startTime=[System.DateTime]::Now
    $OpsMgrScriptName="OpsMgrScript"
    $OpsMgrScriptCount=(Get-PSDrive|where {$_.Name -eq $OpsMgrScriptName}|Measure-Object).Count
    
    if ($OpsMgrScriptCount -ne 1) {
	$tmp = New-PSDrive -Name:$OpsMgrScriptName -PSProvider:OperationsManagerMonitoring -Root:\
    }
    $tmp = New-ManagementGroupConnection OperationsManager
    $computerCriteria="PrincipalName='" + $computerPrincipalName + "'"
    $computerClass=get-monitoringclass -name:Microsoft.Windows.Computer -Path:"$($OpsMgrScriptName):\OperationsManager"
    $computerObject=get-monitoringobject -monitoringclass:$computerClass -criteria:$computerCriteria -Path:"$($OpsMgrScriptName):\OperationsManager"

    if ($Stop) {
        try{
            $endTime=([System.DateTime]::Now).AddSeconds(5)
            Set-MaintenanceWindow -endTime:$endTime -comment:"Stopping Maintenance Mode" -monitoringObject:$computerObject -Reason PlannedOther -ErrorAction "Stop"
            Write-Verbose -Message "Maintenance Mode stopped for server: '$server_'"
        }
        catch{
        Write-Warning "Failed to stop Maintenance mode for server '$server_' $(($_.Exception.Message).substring(0,71))"
        }
    } 
    else {    
    try{
        $endTime=([System.DateTime]::Now).AddMinutes($MaintenanceModeMinutes)
        $currentendtime=(Get-MaintenanceWindow -MonitoringObject:$computerObject).scheduledendtime
        if ($currentendtime -gt $endTime){
            Write-Warning "Maintenance Mode not set for server: '$server_' (Current Maintenance window is set for a longer period: $currentendtime)" -verbose
            break
        }
        New-MaintenanceWindow -startTime:$startTime -endTime:$endTime -monitoringObject:$computerObject -comment:$comment -ErrorAction "Stop"
        Write-Verbose -Message "Maintenance Mode set for server: '$server_'" -verbose
        }
        catch{
            Write-Warning "Failed to set Maintenance mode for server '$server_' $(($_.Exception.Message).substring(0,71))"
        }
    }

}
}