Jere's Techblog

Join Azure VM into AD and install SCCM Client

Enclosed a script to join the Azure machine into AD and install the SCCM client. This is useful if you want to populate a native Azure VM that was not installed with SCCM. To make the AD-join a service user was assigned in the script, certainly not the most beautiful variant but this can be encrypted by a compiled EXE. This can be done with the following tool: https://gallery.technet.microsoft.com/scriptcenter/PS2EXE-GUI-Convert-e7cb69d5

Of course, network access to the AD and SCCM server must be available.

#by Jeremias Kühnis
#02.10.2019

### Vars Section ###
$SCCM_Server = "SCCMPrimary.f.q.d.n"
$site_code = "Your Site Code -like S01"
$SCCM_MPServer = "ManagementPoint.f.q.d.n"
$LocalSource_Path = "$env:SystemDrive\temp\"
$SCCM_ClientInstaller = "$LocalSource_Path" + "Client\ccmsetup.exe"
$SCCM_Repo = "\\$SCCM_Server\SMS_***YOURSITECODE***\Client"
$time = ([datetime]::now).tostring("dd_MM_yyyy_HH-mm-ss")
$ScriptFilename = Split-Path $MyInvocation.MyCommand.Definition -leaf
$LogfileName = $LocalSource_Path + 'Azure-SCCMInstaller' + '__' + $time + '.log'
$fqdn = 'someFQDN'
$JoinADUser = $fqdn + '\ServiceAccountjoinAD'
$JoinADUserPw = 'ServiceAccountPW'
$ADOU_NewAzuewDevice = "OU=Germany,DC=contoso,DC=com"



### External Functions ###
#by   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 


function Write-Log { 
    [CmdletBinding()] 
    Param 
    ( 
        [Parameter(Mandatory = $true, 
            ValueFromPipelineByPropertyName = $true)] 
        [ValidateNotNullOrEmpty()] 
        [Alias("LogContent")] 
        [string]$Message, 
 
        [Parameter(Mandatory = $false)] 
        [Alias('LogPath')] 
        [string]$Path = 'C:\Logs\PowerShellLog.log', 
         
        [Parameter(Mandatory = $false)] 
        [ValidateSet("Error", "Warn", "Info")] 
        [string]$Level = "Info", 
         
        [Parameter(Mandatory = $false)] 
        [switch]$NoClobber 
    ) 
 
    Begin { 
        # Set VerbosePreference to Continue so that verbose messages are displayed. 
        $VerbosePreference = 'Continue' 
    } 
    Process { 
         
        # If the file already exists and NoClobber was specified, do not write to the log. 
        if ((Test-Path $Path) -AND $NoClobber) { 
            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." 
            Return 
        } 
 
        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. 
        elseif (!(Test-Path $Path)) { 
            Write-Verbose "Creating $Path." 
            $NewLogFile = New-Item $Path -Force -ItemType File 
        } 
 
        else { 
            # Nothing to see here yet. 
        } 
 
        # Format Date for our Log File 
        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" 
 
        # Write message to error, warning, or verbose pipeline and specify $LevelText 
        switch ($Level) { 
            'Error' { 
                Write-Error $Message 
                $LevelText = 'ERROR:' 
            } 
            'Warn' { 
                Write-Warning $Message 
                $LevelText = 'WARNING:' 
            } 
            'Info' { 
                Write-Verbose $Message 
                $LevelText = 'INFO:' 
            } 
        } 
         
        # Write log entry to $Path 
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append 
    } 
    End { 
    } 
}

### own Functions ###

function Mount-SCCMShare {
    $DriveName = 'SCCMRepo'
    If (!(Get-PSDrive -name $DriveName -ErrorAction SilentlyContinue)) {
        try {
            New-PSDrive -name $DriveName -PSProvider "FileSystem" -Root  $SCCM_Repo -Credential $credential -Scope Script
        }
        catch {
            Write-Log -Path $LogfileName -Level Error -Message "Can not Mount SCCM Share $SCCM_Repo"
            Exit
        }

    
    }
}

Function SCCMClientInstaller {

    ###########################################################################
    ##          SCCM Client Health check and Troubleshooting Script					
    ##          Author: Lokesh Agarwal
    ##          Date: 23-08-2017
    ##	        Input:- SCCM Client path, MP Address, Site Code
    ##	     Edited: Jeremias Kühnis 27.09.2019 -> Added/Modifying some Code and add some Log Warning
    ###########################################################################




    ############################### Main Code ####################################
    $machinename = hostname


    ############################### Check if WMI is working #######################
    if ((Get-WmiObject -Namespace root\ccm -Class SMS_Client -ErrorAction SilentlyContinue) -and (Get-WmiObject -Namespace root\ccm -Class SMS_Client -ErrorAction SilentlyContinue)) {
        Write-Log -Path $LogfileName -Level Info -Message "WMI is OK"
        $WMI_Status = "Working"
    }
    else {
        Write-Log -Path $LogfileName -Level Warn -Message "Try to Repair WMI"
        Stop-Service -Force winmgmt -ErrorAction SilentlyContinue
        cd  C:\Windows\System32\Wbem\
        Remove-item C:\Windows\System32\Wbem\Repository.old -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$false
        rename-Item Repository Repository.old -ErrorAction SilentlyContinue -Confirm:$false
        Start-Service winmgmt 
    }

    ############################# Check if SCCM Client is installed ##################
    If (Get-Service -Name CcmExec -ErrorAction SilentlyContinue) {
        $Client_Status = "Yes"
        Write-Log -Path $LogfileName -Level Info -Message "SCCM Status OK, Client is installed"
	
        ########### Check if services are running ################################
        $CcmExec_Status = Get-Service -Name CcmExec | % { $_.status }
        $BITS_Status = Get-Service -Name BITS | % { $_.status }
        $wuauserv_Status = Get-Service -Name wuauserv | % { $_.status }
        $Winmgmt_Status = Get-Service -Name Winmgmt | % { $_.status }
        $RRegistry_Status = Get-Service -Name RemoteRegistry | % { $_.status }


        if ($CcmExec_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $CcmExec_Status"
            Get-Service -Name CcmExec | Start-Service
        }

        if ($BITS_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $BITS_Status"
            Get-Service -Name BITS | Start-Service
        }

        if ($wuauserv_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $wuauserv_Status"
            Get-Service -Name wuauserv | Start-Service
        }

        if ($Winmgmt_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $Winmgmt_Status"
            Get-Service -Name Winmgmt | Start-Service
        }

	
	
        $MachinePolicyRetrievalEvaluation = "{00000000-0000-0000-0000-000000000021}"
        $SoftwareUpdatesScan = "{00000000-0000-0000-0000-000000000113}"
        $SoftwareUpdatesDeployment = "{00000000-0000-0000-0000-000000000108}"

        #################### check if Scan cycles are working ###################
        $machine_status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $MachinePolicyRetrievalEvaluation
        $software_status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $SoftwareUpdatesScan
        $softwaredeploy_Status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $SoftwareUpdatesDeployment

        if ($machine_status -and $software_status -and $softwaredeploy_Status) {
            $machine_Rstatus = "Successful"
            Write-Log -Path $LogfileName -Level Info -message "Scan cycles are working $machine_Rstatus"
        }
        else {
            $SMSCli = [wmiclass] "root\ccm:sms_client"
            Write-Log -Path $LogfileName -Level Warn -message "Scan cycles are not working, try to repair SMSCLI-Client"
            $repair = $SMSCli.RepairClient()
            
        }

    }
    else {
        ############## Install SCCM Client ###############################
        Write-Log -Path $LogfileName -Level Info -message "Missing SCCM Client, try to start installation"
        &$SCCM_ClientInstaller /mp:$SCCM_MPServer /logon SMSSITECODE=$site_code
        Start-Sleep 5
        DO {
            $ProcessesFound = (Get-Process -name ccmsetup -ErrorAction SilentlyContinue)
            If (($ProcessesFound) -and ($counter -le "90")) {
                Start-Sleep 10
                $counter++
                Write-Host "Still running: $($ProcessesFound)  $counter"
            }
            Else {
                IF ($counter -gt "90") {
                    Write-Log -Path $LogfileName -Level Error -message "Not OK: Try to kill running Process while InstallationProcess is taking more than 15 Minutes"
                    Get-Process ccmsetup | Stop-Process -Force
                    Exit
                }
                Else {
                    Write-Log -Path $LogfileName -Level Info -message "OK:Process ended by SCCM installer."
                }
            }
        } Until (!$ProcessesFound)

    }

    ####################################################################################################
}

function New-folder {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)][string]$folderpath
    )

    If (!(Test-path $folderpath)) {
        New-Item $folderpath -ItemType Directory
    }
}   

function VerifyPrerequisits {
    #Check Powershell Version
    $psversion = $PSVersionTable.PSVersion.Major
    IF ($psversion -ge "5") {
        Write-Log -Path $LogfileName -Level Info -Message "Powershell Version is OK – $psversion"
    }
    Else {
        Write-Log -Path $LogfileName -Level Error -Message "Missing PowerShell or installed Version is to low  – $psversion "
        $Errorcouonter = "1"
    }
    
    #Check .Net FrameWork Version
    $DotNetVersion = (Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\' | Get-ItemPropertyValue -Name Version)
    IF (Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\' | Get-ItemPropertyValue -Name Release | Foreach-Object { $_ -ge 461814 }) {
        Write-Log -Path $LogfileName -Level Info -Message ".NetVersion ist OK – $DotNetVersion"
    }
    else {
        Write-Log -Path $LogfileName -Level Error -Message "Missing .NetFramework or installed Version is to low  – $DotNetVersion "
        $Errorcouonter = "1"
    }

    #Verify SourcePath
    IF (!(Test-Path $SCCM_Repo) ) {
        Write-Log -Path $LogfileName -Level Error -Message "Can not find SCCM installer Path: $SCCM_Repo"
        $Errorcouonter = "1"
    }

    ##Last Part
    If ($Errorcouonter -eq "1") {
        Write-Log -Path $LogfileName -Level Warn -Message "Installation is not performed because the checked prerequisites are Wrong/Missing. See further up in the log."
        exit
    }
    Else {
        Write-Log -Path $LogfileName -Level Info -Message "Prerequisits are Okay, try to lead trough installation"
    }


}

Function Remove-InstallerLocation {
    $removepaths = @(
        "$LocalSource_Path\Client"
    )
    foreach ($ToDelPath in $removepaths) {
        Remove-Item $ToDelPath -Recurse -Force -ErrorAction SilentlyContinue
    }
}

###################
### Main Script ###
###################
$password = $JoinADUserPw | ConvertTo-SecureString -asPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($JoinADUser, $password)

### Create C:\Temp Folder, if not Exists for logs ###
New-folder -folderpath $LocalSource_Path


### Join AD-Section ###
#Need to Add credentials and start Job for OU path
$Error.Clear()
Try {
    Add-Computer -DomainName $fqdn -Credential $credential -OUPath $ADOU_NewAzuewDevice -Force -ErrorAction Stop
    
    
}
catch [System.Net.WebException], [System.Exception] {
    Write-Log -Path $LogfileName -Level Error -Message "Failed @ Ad-join Part...maybe ADAccount already exists."
    Write-Log -Path $LogfileName -Level Error -Message $Error
    Exit
}

Mount-SCCMShare
VerifyPrerequisits


### Install SCCM-Client Section ###
Write-Log -Path $LogfileName -Level Info -message "Start-SCCM Repair/ Install Process..."
Copy-Item $SCCM_Repo -Recurse -Destination $LocalSource_Path -Force
SCCMClientInstaller
Write-Log -Path $LogfileName -Level Info -message "End Section: 'Start-SCCM Repair/ Install Process...'"
Write-Log -Path $LogfileName -Level Info -message "Finished all Jobs, if there are any Problems Check if Computer is joined in AD and Check the Execution Policy. Also Check de Install status of SCCM Client."
Write-Log -Path $LogfileName -Level Info -message "Try to clean Up local install Files"
Start-Sleep 3
Remove-InstallerLocation
Write-Log -Path $LogfileName -Level Info -message "Successfully finish..."

#reboot while AD-Join
shutdown -r -f -t 60
Continue reading...

Add License to all O365 Users trough Powershell

First of all you need to install the Powershell Module and Connect to the MSOnline Serivce

Install-Module MSOnline
Import-Module *
Connect-MsolService -Credential (get-credential)

You can get an overview of all Users trough this Command:

Get-MsolUser

This script block can be used to assign a license to any user who is not a licensed user.

This example assumes that the command “(Get-MsolAccountSku).accountskuid” retrive only one value/license. If you have several licenses you have to specify this for the variable “$SKUID“.

#byJKU 29.04.2019 
#Activate each user with MSolAccountSKU License
$SKUID= (Get-MsolAccountSku).accountskuid

IF ((Get-MsolUser -UnlicensedUsersOnly).UserPrincipalName){
    (Get-MsolUser -UnlicensedUsersOnly).UserPrincipalName | % {
    Set-MsolUserLicense -UserPrincipalName $_ -AddLicenses $SKUID
    }
}Else{
    Write-host "There is no User without License" -ForegroundColor Yellow
}

Continue reading...

Visual Studio Code

One of my favorite editors for editing Powershell scripts is Visual Studio Code. Mircosoft’s OpenSource Code Editor, launched in 2016, is a wonderful editor and the biggest advantage is that it works on Windows, Linux and Mac.

In this article I want to show some advantages why I prefer this editor to the classic Powershell_Ise, Atom Editor and Notepad++. I also show useful addons and editor settings.

Okay first of all i’ll show you why

At the beginning I will show you the advantages of the editor:

  • The editor is very fast (no lags) and it starts very fast
  • The editor is with approx. 180MB installation size relatively slim in contrast to Visual Studio
  • The editor supports various programming and scripting languages, which can be installed using extensions.
  • Many Addons/Extensions (Debugger, DebugConsole, ColorEditors, Autocorrection, Sourcecontrol, GIT, TFS Server, Docker, various Azure Tools and Connections)
  • Code can be executed within the editor.
  • Integrated Terminal Console
  • Many configuration options (autosave, color selection, editor behavior, code arrangement and much more).
  • Command Explorer
  • Various color themes for the editor itself (dark / light, much based on Visual Studio)
  • Configuration can be easily done using .json files or GUI
  • Has a very large user community and is strongly pushed by Microsoft.

Distinctive differences to Visual Studio Editor:

  • Visual Studio Code organizes itself according to folder structures (file system) and not like Visual Studio with “Projects”
  • No integrated editor for Windows WPF/Windows Form GUI’s.
  • No Enterprise Debugging (CPU Runtime)

Those are my prefferd Custom Settings:

I have made the following setting in the JSON file (User Settings) to make the scripten more pleasant.

“powershell.integratedConsole.focusConsoleOnExecute”: false,

“powershell.scriptAnalysis.enable”: true,

“powershell.codeFormatting.openBraceOnSameLine”: true

Continue reading...