PowerShell – Interpret NPS logs

Posted: January 17, 2024 in Scripts

Here is script for reading Network policy server (NPS logs)

# How to read NPS log files - https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-R2-and-2008/cc771748(v=ws.10)?redirectedfrom=MSDN
$columns = "ComputerName","ServiceName","Record-Date","Record-Time","Packet-Type","User-Name","Fully-Qualified-Distinguished-Name","Called-Station-ID","Calling-Station-ID","Callback-Number","Framed-IP-Address","NAS-Identifier","NAS-IP-Address","NAS-Port","Client-Vendor","Client-IP-Address","Client-Friendly-Name","Event-Timestamp","Port-Limit","NAS-Port-Type","Connect-Info","Framed-Protocol","Service-Type","Authentication-Type","Policy-Name","Reason-Code","Class","Session-Timeout","Idle-Timeout","Termination-Action","EAP-Friendly-Name","Acct-Status-Type","Acct-Delay-Time","Acct-Input-Octets","Acct-Output-Octets","Acct-Session-Id","Acct-Authentic","Acct-Session-Time","Acct-Input-Packets","Acct-Output-Packets","Acct-Terminate-Cause","Acct-Multi-Ssn-ID","Acct-Link-Count","Acct-Interim-Interval","Tunnel-Type","Tunnel-Medium-Type","Tunnel-Client-Endpt","Tunnel-Server-Endpt","Acct-Tunnel-Conn","Tunnel-Pvt-Group-ID","Tunnel-Assignment-ID","Tunnel-Preference","MS-Acct-Auth-Type","MS-Acct-EAP-Type","MS-RAS-Version","MS-RAS-Vendor","MS-CHAP-Error","MS-CHAP-Domain","MS-MPPE-Encryption-Types","MS-MPPE-Encryption-Policy","Proxy-Policy-Name","Provider-Type","Provider-Name","Remote-Server-Address","MS-RAS-Client-Name","MS-RAS-Client-Version"
 
$packettype = @{
  "1" = "Access-Request"
  "2" = "Access-Accept"
  "3" = "Access-Reject"
  "4" = "Accounting-Request"
}
 
$authenticationtype = @{
  "1" = "PAP"
  "2" = "CHAP"
  "3" = "MS-CHAP"
  "4" = "MS-CHAP v2"
  "5" = "EAP"
  "7" = "None"
  "8" = "Custom"
}
 
$reasoncode = @{
  "0" = "IAS_SUCCESS"
  "1" = "IAS_INTERNAL_ERROR"
  "2" = "IAS_ACCESS_DENIED"
  "3" = "IAS_MALFORMED_REQUEST"
  "4" = "IAS_GLOBAL_CATALOG_UNAVAILABLE"
  "5" = "IAS_DOMAIN_UNAVAILABLE"
  "6" = "IAS_SERVER_UNAVAILABLE"
  "7" = "IAS_NO_SUCH_DOMAIN"
  "8" = "IAS_NO_SUCH_USER"
  "16" = "IAS_AUTH_FAILURE"
  "17" = "IAS_CHANGE_PASSWORD_FAILURE"
  "18" = "IAS_UNSUPPORTED_AUTH_TYPE"
  "32" = "IAS_LOCAL_USERS_ONLY"
  "33" = "IAS_PASSWORD_MUST_CHANGE"
  "34" = "IAS_ACCOUNT_DISABLED"
  "35" = "IAS_ACCOUNT_EXPIRED"
  "36" = "IAS_ACCOUNT_LOCKED_OUT"
  "37" = "IAS_INVALID_LOGON_HOURS"
  "38" = "IAS_ACCOUNT_RESTRICTION"
  "48" = "IAS_NO_POLICY_MATCH"
  "64" = "IAS_DIALIN_LOCKED_OUT"
  "65" = "IAS_DIALIN_DISABLED"
  "66" = "IAS_INVALID_AUTH_TYPE"
  "67" = "IAS_INVALID_CALLING_STATION"
  "68" = "IAS_INVALID_DIALIN_HOURS"
  "69" = "IAS_INVALID_CALLED_STATION"
  "70" = "IAS_INVALID_PORT_TYPE"
  "71" = "IAS_INVALID_RESTRICTION"
  "80" = "IAS_NO_RECORD"
  "96" = "IAS_SESSION_TIMEOUT"
  "97" = "IAS_UNEXPECTED_REQUEST"
}
 
 
 
# Define the headers
$headers = @("ComputerName","ServiceName","Record-Date","Record-Time","Packet-Type","User-Name","Fully-Qualified-Distinguished-Name","Called-Station-ID","Calling-Station-ID","Callback-Number","Framed-IP-Address","NAS-Identifier","NAS-IP-Address","NAS-Port","Client-Vendor","Client-IP-Address","Client-Friendly-Name","Event-Timestamp","Port-Limit","NAS-Port-Type","Connect-Info","Framed-Protocol","Service-Type","Authentication-Type","Policy-Name","Reason-Code","Class","Session-Timeout","Idle-Timeout","Termination-Action","EAP-Friendly-Name","Acct-Status-Type","Acct-Delay-Time","Acct-Input-Octets","Acct-Output-Octets","Acct-Session-Id","Acct-Authentic","Acct-Session-Time","Acct-Input-Packets","Acct-Output-Packets","Acct-Terminate-Cause","Acct-Multi-Ssn-ID","Acct-Link-Count","Acct-Interim-Interval","Tunnel-Type","Tunnel-Medium-Type","Tunnel-Client-Endpt","Tunnel-Server-Endpt","Acct-Tunnel-Conn","Tunnel-Pvt-Group-ID","Tunnel-Assignment-ID","Tunnel-Preference","MS-Acct-Auth-Type","MS-Acct-EAP-Type","MS-RAS-Version","MS-RAS-Vendor","MS-CHAP-Error","MS-CHAP-Domain","MS-MPPE-Encryption-Types","MS-MPPE-Encryption-Policy","Proxy-Policy-Name","Provider-Type","Provider-Name","Remote-Server-Address","MS-RAS-Client-Name","MS-RAS-Client-Version")
 
# Read the file content
$content = Get-Content -Path "C:\Windows\System32\LogFiles\IN2401.log" -Encoding UTF8
 
# Convert the content to a collection of PSObjects
$csv = $content | ConvertFrom-Csv -Delimiter ',' -Header $headers
 
# Create an empty array to hold the output objects
$output = @()
 
foreach ($row in $csv) {
    # Create a new PSObject for each row
    $obj = New-Object PSObject
    foreach ($property in $row.PSObject.Properties) {
        if ($property.Value) {
            switch ($property.Name) {
                "Packet-Type" {
                    $obj | Add-Member -MemberType NoteProperty -Name $property.Name -Value $($packettype[$property.Value])
                }
                "Authentication-Type" {
                    $obj | Add-Member -MemberType NoteProperty -Name $property.Name -Value $($authenticationtype[$property.Value])
                }
                "Reason-Code" {
                    $obj | Add-Member -MemberType NoteProperty -Name $property.Name -Value $($reasoncode[$property.Value])
                }
                default {
                    $obj | Add-Member -MemberType NoteProperty -Name $property.Name -Value $property.Value
                }
            }
        }
    }
    # Add the new object to the output array
    $output += $obj
}
 
# Display the output in a grid view
$output | Out-GridView

I’m getting to my private hotmail mailbox monthly internet bills. Based on name of user on the bill, i need to forward these emails to other people, so i created python script to automate process.

I use basic authentication to login to mailbox, but for office 365, Microsoft disabled basic authentication so other methods must be used.

Script first truncate log file when number of lines is more than 500 lines (delete first 300 lines), checks if there is any email with SBB in subject, if it finds it, it searches for PDF attachment and search for the user name which is located between UPLATILAC and BAČKA 21 /E, 11080 strings, in this case i need to extract PITER PARKER

Of course, i could just search for PITER PARKER string, but wanted to learn how to find string between UPLATILAC and BAČKA 21 /E
Here is content of PDF file exported in variable:

SBB d.o.o. Beograd, Buleva r Peka Dap čevića 19, 11010 Beograd, Srbija WEB  www.sbb. rs
SBB d.o .o. Beogra d,
Bulevar Peka Dapčevića 19,
11010 Beogra d, SrbijaSBB d.o.o. Beogra d,
Bulevar Peka Dapčevića 19,
11010 Beogra d, SrbijaTrajanje/broj poziva Broj telefona
PITER PARKER
BAČKA 21 /E
11080 BEOGRAD
PAK: 200636  REJON: 028Račun broj: 9071975355
Broj ugovora: 101618515
ID: 2010493930
Tekući račun: 170-772-26
Poziv na broj: 362010493930
Rok plaćanja: 25.12.2023
Period izvršenja usluga: 01.11.2023 - 30.11.2023
Datum prometa usluga: 30.11.2023
Datum izdavanja računa: 30.11.2023
Mesto izdavanja računa: BEOGRAD
Rok za podnošenje prigovora: 30 DANA
DIGITALNI DUO TV+NET SILVER 1.378,33 275,67 1.654,00
Mesečno održavanje 1.370,83 274,17 1.645,00
Račun za NOVEMBAR 2.749,16 549,84 3.299,00
Hvala Vam što redovno izmirujete svoj SBB račun3.299,00
189 RSD 3.299,00
170-772-26
97 362010493930189 RSD 3.299,00
170-772-26
97 362010493930Uplatilac
PITER PARKER
BAČKA 21 /E, 11080 BEOGRAD
Svrha uplate: Usluge Broj ugovora: 101618515
Rok plaćanja: 25.12.2023 ID: 2010493930
UPLATILAC
PITER PARKER
BAČKA 21 /E, 11080
BEOGRAD
Ukoliko ste već izmirili svoju obavezu zanemarite nalog za uplatu. SVRHA UPLATE: Usluge   BROJ UGOVORA: 101618515  ROK PLAĆANJA: 25.12.2023
NBS IPS QR

Once string is found, whole email (with attachment) is forwarded to some email address and email is deleted from mailbox

import imaplib 
import email
from email.header import decode_header 
import tempfile 
import PyPDF2
from email.parser import BytesParser
import smtplib 
from email.mime.multipart import MIMEMultipart 
from email.mime.text import MIMEText 
import logging 
import datetime

LOG_FILE = 'C:/temp/emails.log'
USER = 'myemail@hotmail.com' 
MAILBOX_PASSWORD = 'password'

# Truncating log file: if number of line is more than 500, delete first 300 lines
with open(LOG_FILE, 'r+') as file:
    lines = file.readlines()
    if len(lines) > 500:
        # Move to the start of the file and delete the first 300 lines
        file.seek(0)
        file.writelines(lines[300:])
        # Truncate any remaining content (if the file was shortened)
        file.truncate()

logging.basicConfig(level = logging.DEBUG, 
                    format = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',  
                    filename = LOG_FILE,  
                    filemode = 'a')  

def forward_email(to_email, email_id): 
    try: 
        forwarded_email = MIMEMultipart() 
        forwarded_email['From'] = USER 
        forwarded_email['To'] = to_email 
        forwarded_email['Subject'] = f"Forwarded Email: SBB racun" 
        forwarded_email.attach(email.message_from_bytes(raw_email)) 
        # Connect to SMTP server and send the email 
        with smtplib.SMTP("outlook.office.com", 587) as server: 
            server.starttls() 
            server.login(USER, MAILBOX_PASSWORD) 
            server.send_message(forwarded_email) 
            msgid = email_id 
            logging.info("Email forwarded successfully to " + to_email) 
    except Exception as e:
      logging.critical("An error has occurred:")
      logging.critical(e, exc_info=True)  

# IMAP settings 
IMAP_SERVER = 'outlook.office.com' 
USERNAME = USER
PASSWORD = MAILBOX_PASSWORD
MAILBOX = 'INBOX' 

# Connect to the IMAP server
try:
    mail = imaplib.IMAP4_SSL(IMAP_SERVER) 
    mail.login(USERNAME, PASSWORD) 
    mail.select(MAILBOX)
    logging.info("Logged in to mailbox") 

    # Search for emails with the subject "SBB"
    logging.info("Searching for emails with subject SBB")
    status, messages = mail.search(None, '(SUBJECT "SBB")')  # Search for emails with the subject "SBB"
    messages = messages[0].split() 
    for msg_id in messages: 
       # Fetch email 
       status, data = mail.fetch(msg_id, '(RFC822)') 
       raw_email = data[0][1] 
       email_message = email.message_from_bytes(raw_email) 
       # Get the email subject
       subject = email_message.get('Subject')
       # Check if the subject is "SBB" and process only if there's an attachment
       if subject and 'SBB' in subject.upper():  # Case-insensitive check for 'SBB'
          logging.info("Found SBB emails, parsing started.")
        # Iterate through each part of the email 
          for part in email_message.walk(): 
            if part.get_content_maintype() == 'multipart': 
                continue 
            if part.get('Content-Disposition') is None: 
                continue 
            # Check if part is an attachment 
            filename = part.get_filename() 
            if filename: 
                filename_bytes, encoding = decode_header(filename)[0] 
                if isinstance(filename_bytes, bytes): 
                    filename = filename_bytes.decode(encoding or 'utf-8') 
                else: 
                    filename = filename_bytes

                # Check if the attachment content type is a PDF 
                content_type = part.get_content_type() 
                if content_type == 'application/pdf': 
                    attachment_data = part.get_payload(decode=True) 
                    if attachment_data: 
                        # Save attachment data to a temporary file 
                        with tempfile.NamedTemporaryFile(delete=False) as temp_file:
                           # prevent piping number of written characters to the console
                            _ = temp_file.write(attachment_data) 
                            temp_file_name = temp_file.name 
                        # Read the saved temporary file using PyPDF2 
                        with open(temp_file_name, 'rb') as temp_pdf: 
                            pdf_reader = PyPDF2.PdfReader(temp_pdf) 
                            pdf_text = "" 
                            for page in pdf_reader.pages: 
                                pdf_text += page.extract_text()
                                output = pdf_text
                                start_marker = 'UPLATILAC' 
                                end_marker = 'BAČKA 21 /E'
                                start_index = output.find(start_marker) 
                                end_index = output.find(end_marker, start_index) if start_index != -1 else -1
                                if start_index != -1 and end_index != -1: 
                                  extracted_text = output[start_index + len(start_marker):end_index].strip() 
                                if 'PITER PARKER' in extracted_text: 
                                   target_email_id = msg_id  # Store the email id that matches 
                                   forward_email("spiderman@gmail.com", target_email_id) 
                                   mail.store(target_email_id, '+FLAGS', '\\Deleted') 
                                   logging.info("Email has been processed and deleted") 
                                elif 'TONY STARK' in extracted_text: 
                                   target_email_id = msg_id  # Store the email id that matches 
                                   forward_email("ironman@gmail.com", target_email_id) 
                                   mail.store(target_email_id, '+FLAGS', '\\Deleted')
                                   logging.info("Email has been processed and deleted") 
                                else: 
                                   logging.debug("Nothing to process")
except Exception as e:
      logging.critical("An error has occurred:")
      logging.critical(e, exc_info=True)           
    
            

Below script will get top services by CPU/Memory consumption,CPU usage is returned in percent, it returns service name, processID related to related service and memory/CPU usage

Function Service-Top-Memory-Usage{
[cmdletbinding()]
Param (
[parameter(Mandatory=$true)]
[string]$top
)
# End of Parameters
 
Process{
  # Get all running services
  $services = Get-CimInstance -ClassName Win32_Service | Where-Object { $_.State -eq 'Running' }
  # Create an array to store service information
  $serviceInfo = @()
  # Iterate through each service to get process memory usage
  foreach ($service in $services) {
    $serviceName = $service.Name
    $processId = $service.ProcessId
    $process = Get-Process -Id $processId -ErrorAction SilentlyContinue
     
    if ($process) {
        $memoryUsageMB = [math]::Round($process.WorkingSet64 / 1MB, 2)
         
        $serviceInfo += New-Object PSObject -Property @{
            'ServiceName' = get-service -Name $serviceName | select -ExpandProperty  DisplayName
            'MemoryUsageMB' = $memoryUsageMB
            'ProcessID' = $processId
  }
 }
}
 
# Sort the service info array by memory usage
$topServices = $serviceInfo | Sort-Object -Property MemoryUsageMB -Descending | Select-Object -First $top
return $topServices
 }
}
 
# Usage example
Service-Top-Memory-Usage -top 5

   # --------------------------------Top service CPU usage---------------------------------------------  

Function Service-Top-CPU-Usage{
[cmdletbinding()]
Param (
[parameter(Mandatory=$true)]
[string]$top
)
# End of Parameters
  
Process{
     $ServicePIDs = Get-CimInstance -ClassName Win32_Service | Where ProcessId -ne 0 | Select Name,DisplayName,ProcessId
     $LogicalCPUs = Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty NumberOfLogicalProcessors
     $logicalProcessors = ''
     # If there are more than 1 CPU, get sum of logical processors for all CPUs
     if ($LogicalCPUs -is[array]){
        $numberofLogicalCPUs = @()
          foreach ($LogicalCPU in $LogicalCPUs){
            $numberofLogicalCPUs += $LogicalCPU
          }
     $logicalProcessors = ($numberofLogicalCPUs | Measure-Object -Sum).Sum
      }
      else {
         $logicalProcessors = $LogicalCPUs
      }
     Get-CimInstance -ClassName Win32_PerfFormattedData_PerfProc_Process | Where 
 ($ServicePIDs.ProcessId -contains $_.IDProcess) -and ($_.Name -notmatch "_total|idle")} `
     | Select @{N="DisplayName";E={($ServicePIDs | Where ProcessId -eq $_.IDProcess).DisplayName}},`
     @{N="CPUPercentUsage";E={($_.PercentProcessorTime/($logicalProcessors * 100))*100}},`
     IDProcess | Sort CPUPercentUsage -Descending | Select-Object -First $top
 }
}


# Usage example
Service-Top-CPU-Usage -top 3

In case you often need to add files to WMP playlist or any other XML file, take a look at this script

WMP has below format, which means new files should be added below <seq> tag

  • Script first checks if there are any mp3 file in Downloads folder, and will move it to location where other mp3 files are stored
  • Script then will check if there is already file where is stored last time when script was running, and if yes, it will look for files which were added after script was ran last time, obviously, if script runs for the first time, that file won’t be present, in that case, script would scan all MP3 files in the directory and will check if file name is missing from the playlist and will try to add it.
  • At the end, script will add “marker” so it won’t scan all files, but only those created after script was run last time
$mp3Path = "C:\Istorija\"
$playList = "C:\Users\user\Music\Playlists\1.wpl"
$mp3DownloadPath = "C:\Users\user\Downloads\"
$scriptRunLastTimePath = "$mp3Path\date.txt"
$directoryInfo = Get-ChildItem -Path $mp3DownloadPath -Filter "*.mp3" -File | Measure-Object

# If any mp3 file in Download folder, move it to $mp3Path
$count = $directoryInfo.Count
Try{
if($count -eq 0){
  Write-Host "Nothing to move to $mp3Path" -ForegroundColor Magenta
}
else{
   Get-ChildItem -Path $mp3DownloadPath -Filter "*.mp3" -Recurse -File | Move-Item -Destination $mp3Path -Force
  }
}
Catch{
    $Error[0]
}
# If File with datetime when script was executed exists, get mp3 files created after script ran last time and store them in the $mp3Files variable

Try{
    if (Test-Path $scriptRunLastTimePath -PathType Leaf) {
        $dateToCompareWith = Get-Content -Path $scriptRunLastTimePath
        $culture = [System.Globalization.CultureInfo]::InvariantCulture
        $format = 'dd.MM.yyyy HH:mm:ss'
        $storedDateTime = [DateTime]::ParseExact($dateToCompareWith, $format, $culture)
        $mp3Files = @(Get-ChildItem -Path $mp3Path -Filter "*.mp3" -File |
            Where-Object { $_.LastWriteTime -gt $storedDateTime })
   } else {
        $mp3Files = Get-ChildItem -Path $mp3Path -Filter "*.mp3" -File 
 }
}
Catch{
     $Error[0]
}

# Get current files in playlist
($xml = [xml]::new()).Load($playList)
$currentFilesInPlaylist = $xml.SelectNodes("//seq/*").src

# If mp3 file is present in folder and not in 1.wpl, add the full file name to playlist file after the <seq> tag

foreach ($mp3File in $mp3Files) {
        Try{
            # Note the use of -notin and the *whole array* of files in the playlist.
            if ($mp3File.FullName -notin $currentFilesInPlaylist) {
               (Get-Content $playlist) | Foreach-Object {
                  $_ # send the current line to output
                    if ($_ -match "<seq>") {
                    # Add lines after the selected pattern
                    '             <media src="' + $mp3File.FullName + '"/>'
                }
               } | Set-Content $playList -Force
              $output = "File " + $mp3File.FullName + " has been added to playlist"
              Write-Host $output -ForegroundColor Magenta
           } else {
                 Write-Host "Nothing to add" -ForegroundColor Green
  }
}
      Catch{
       $Error[0]
 }
}

# Create a "checkpoint" file: Export the date and time the script was last run to the file

(Get-Date).ToString('dd/MM/yyyy HH:mm:ss') | Out-File -FilePath $scriptRunLastTimePath

We’re monitoring time synchorization with NTP server by Nagios check_ntp_time script. It works fine for all but one server. We’re getting a lot of NTP WARNING alerts, but as soon as manually sync time with NTP server, alerts is gone, so to prevent these alerts, i created script scheduled by cron to check if NTP offset is less than -2 or greather than 2, if yes, run synchronization with NTP server

# Capture script output to log file
logfile=/var/log/ntp_sync.txt
exec >> $logfile
exec 2>&1
ntp_server="10.10.2.3"

# Get number of lines of log file
lines=$(sed -n '$=' $logfile)
# If log file has more than 400 lines, trim first 300 lines
if [ "$lines" -ge 400 ]
then
  sed -i '1,300d' $logfile
fi

# Get current NTP offset
offset=$(/usr/sbin/ntpdate -q $ntp_server | head -1 | cut -d " " -f 6 | sed "s/.$//")
# If current NTP offset is less than -2 or greater than 2, run sync with NTP server
if [ "${offset%.*}" -le -2 ] || [ "${offset%.*}" -ge 2 ]
 then
/usr/sbin/ntpdate $ntp_server
echo "$(date): sync with NTP server"
/sbin/service ncpa_passive restart
 else
echo "$(date): sync OK"
fi

I had task to check who and when is accessing file shares

First step was to enable folder/files audit

Then i should pull reports out of Event Viewer

Needed to get reports only for subset of shared folders and needed to exclude specific accounts from it

Below script returns time, folder being accessed and account who accessed it.

$EventId = 4663
$results = Get-WinEvent -FilterHashtable @{logname='Security'; id=$EventId; StartTime = "03/24/2023 09:30:00" } |`
Where-Object { $_.message -match "C:\\folder1\\" -or $_.message -match "D:\\folder2" -or $_.message -match "D:\\folder3" -and $_.message -notmatch "Account Name:\s*account1*" -and $_.message -notmatch "Account Name:\s*machine$*"}`
| Select-Object -Property TimeCreated, 
                            @{Label='Account'; Expression={$_.properties[1].Value}}, 
                            @{Label='ObjectName'; Expression={$_.properties[6].Value}}

$results | Export-Csv "C:\1.csv" -NoTypeInformation -Encoding UTF8

Even after creating /etc/wsl.conf

[network]
generateResolvConf = false

And specifying desired DNS server in /etc/resolv.conf, after WSL reboot, /etc/resolv.conf reverts it’s content.

Here is how to make changes in /etc/resolv.conf make permanent:

sudo rm /etc/resolv.conf
sudo bash -c 'echo "nameserver 1.1.1.1" > /etc/resolv.conf'
sudo bash -c 'echo "[network]" > /etc/wsl.conf'
sudo bash -c 'echo "generateResolvConf = false" >> /etc/wsl.conf'
sudo chattr +i /etc/resolv.conf

chattr +i command makes file immutable so /etc/resolv.conf file cannot be modified nor deleted.

I created simple script to install FortiClient and put script and setup file in same folder and instead of specifying full path to FortiClientVPN.msi, wanted for script to locate full path of setup folder. Script will run if right click on it and select “Run with PowerShell”. Script will check if current user is Administrator, and if yes, it will elevate Powershell process, it’s equivalent to Run As admin.

Install.ps1:

param([switch]$Elevated)

function Test-Admin {
    $currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
    $currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

if ((Test-Admin) -eq $false)  {
    if ($elevated) {
        # tried to elevate, did not work, aborting
    } else {
        Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
    }
    exit
}

# Restart Process using PowerShell 64-bit 
If ($ENV:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
   Try {
       &"$ENV:WINDIR\SysNative\WindowsPowershell\v1.0\PowerShell.exe" -File $PSCOMMANDPATH
   }
 Catch {
     Throw "Failed to start $PSCOMMANDPATH"
 }
    Exit
}

# Save the current location and switch to this script's directory.
# Note: This shouldn't fail; if it did, it would indicate a
# serious system-wide problem.

$prevPwd = $PWD; Set-Location -ErrorAction Stop -LiteralPath $PSScriptRoot

try {
 # Your script's body here.
 # Install FortiClient VPN
  Start-Process Msiexec.exe -Wait -ArgumentList '/i FortiClientVPN.msi REBOOT=ReallySuppress /qn'
 # ... 
 $PWD  # output the current location 
}
finally {
  # Restore the previous location.
  $prevPwd | Set-Location
}

This script will check if there are any *.xml files created for current day and if yes, it will download it. Service principal is used for Azure authentication

Creating Service principal

In Azure portal, select Azure Active Directory – App registrations

New registration

Give name and click Register

Click on application – Certificates & secrets – Client secrets – New client secret

Create secret and write it

Click overview and write down application ID and tenant ID

Adding contributor role to Service principal on storage account

On storage account select Access control (IAM) – Add – Add role assignment

Select contributor – Next

Assign access to: User, group or service principal, select service principal

Service principal is used for Azure authentication.ApplicationID, service principal password, subscription and tenant ID are stored in encrypted file.

SSL cert is used for encyption/decryption credentials file.

Create credentials.csv file, example bellow:

appID, appPassword, tenantID, subscriptionID
11-222-3333,secret, 11-224-fggg-sddd,sssss-2222-122222

Encrypt credentials.csv file using one of previous posts, i described procedure in detail so won’t repeat it again.

# Error handling
Function Exception {
     $err = $_.Exception.Message
     write-output $err | timestamp >> $LogFile
     return $err   
 }
 

# Create logs directory and file if not exist
$LogFile = "C:\Navision\Logs\log.txt"
 
If (-not(Test-Path -Path $LogFile)){
    New-Item -Path $LogFile -ItemType File -Force -ErrorAction Stop
}
 
$module = "Az" 
filter timestamp {"$(Get-Date -Format G): $_"}
$resourceGroupName = "myResourceGroup" 
$storageAccName = "myStorageAccount" 
$container_name = "myContainer"
$downloadLocation = "C:\Navision\Downloads"
 
 
# Truncate log file
 
# Get number of lines of log file
$logfileLines = Get-content $LogFile | Measure-Object –Line | select -ExpandProperty Lines
if($logfileLines -gt '5000') {
    (Get-Content $LogFile | Select-Object -Skip 4000) | Out-File $LogFile
  }
 
 
# Create download location if not exists
If (-not(Test-Path -Path $downloadLocation)){
    New-Item -Path $downloadLocation -ItemType Directory -ErrorAction Stop 
}
 
 
Try{
   Write-Output "Decrypting credentials file" | timestamp >> $LogFile
   $CSV_F = Unprotect-CmsMessage -To "*svc-account@example.com*" -Path C:\Navision\credentials.csv.cms -ErrorAction Stop
}
Catch{
    Exception
}
 
 
$Data = $CSV_F | ConvertFrom-Csv
foreach($i in $Data){
   $appID = $i.appID
   $appPassword = $i.appPassword
   $tenantID = $i.tenantID
   $subscriptionID = $i.subscriptionID
}
 
Write-Output "File decrypted" | timestamp >> $LogFile
 
$azureAppCred = (New-Object System.Management.Automation.PSCredential $appID, ( $appPassword | ConvertTo-SecureString -AsPlainText -Force))
 
# Connect to Azure
# Install Az module if not exists
if (-Not (Find-Module -Name $module | Where-Object {$_.Name -eq $module})) {
    Install-Module -Name $module -Force -Verbose | Import-Module
} 
else {
    Write-Host "Module $module exist"
}
 
Write-Output "Connecting to Azure" | timestamp >> $LogFile
Connect-AzAccount -ServicePrincipal -SubscriptionId $subscriptionID -TenantId $tenantID -Credential $azureAppCred -ErrorAction Stop | Out-null
Write-Output "Connection to Azure finished" | timestamp >> $LogFile
 
# Get the storage account
 
Try{
    
   Write-Output "Connecting to storage account $storageAccName and container 
   $container_name " | timestamp >> $LogFile
   $storageAcc = Get-AzStorageAccount -ResourceGroupName $resourceGroupName -Name 
   $storageAccName -ErrorAction Stop
   ## Get the storage account context 
   $ctx=$storageAcc.Context
   ## Get container and get the xml file(s) created today
   $blobContents = Get-AzStorageBlob -Container $container_name  -Context $ctx -Blob "*.xml" -ErrorAction Stop | Where-Object{$_.LastModified.DateTime -gt ((Get-Date).Date)}
   Write-Output "Successfully connected to  storage account $storageAccName and container $container_name " | timestamp >> $LogFile
}
Catch{
   Exception
}
 
# Download file(s) from Storage account, if file exists
If ($blobContents){
Try{
       ForEach($blobContent in $blobContents){
          $output = "Found file(s) to download:" + $blobContent.Name
          Write-Output $output | timestamp >> $LogFile
          Get-AzStorageBlobContent -Container $container_name  -Context $ctx -Blob 
          $blobContent.Name -Destination $downloadLocation -Force -ErrorAction Stop
          $output = "Successfully downloaded file(s):" + $blobContent.Name
          Write-Output $output | timestamp >> $LogFile
    }
   }
 Catch{
      Exception
 }
}
Else{
  Write-Output "Nothing to download" | timestamp >> $LogFile
}  

PowerShell – disk cleanup script

Posted: September 16, 2022 in Scripts

Script checks if C:\Windows\SoftwareDistribution\Download, C:\Windows\Temp,C:\Windows\ccmcache and user profiles folders are empty, if not delete it’s content, delete IIS logs older than 60 days, delete uknown user profiles and compress WinSxS  folder if disk usage is more than 85%

 Function Check-if-empty-folder {
[cmdletbinding()]
Param (
[parameter(Mandatory=$true)]
[string]$folderName
 
)
$directoryInfo = Get-ChildItem $folderName | Measure-Object
$count = $directoryInfo.count
if($count -eq 0){
  return 0
}
else{
  return 1
 }
}
 
 
 
$Daysback = "-60"
$CurrentDate = Get-Date
$DatetoDelete = $CurrentDate.AddDays($Daysback)
$systemDrive = Get-ChildItem -Path Env:\SystemDrive | select -ExpandProperty Value
$iisPath = "$systemDrive\inetpub\logs\LogFiles\"
$foldersToCheck = @("$systemDrive\Windows\ccmcache", "$systemDrive\Windows\SoftwareDistribution\Download", "$systemDrive\Windows\Temp", "$systemDrive\Users\*\AppData\Local\Temp\*")
 
# Delete IIS logs older than 60 days
 
if(Test-Path $iisPath){
  $iisLogs = Get-ChildItem -Recurse $iisPath | Where-Object { (! $_.PSIsContainer) -and ($_.LastWriteTime -lt $DatetoDelete) }
  foreach ($iisLog in $iisLogs){
     Try{
        Remove-Item $iisLog.FullName -Force -ErrorAction Stop
        Write-Host "Deleted: " $iisLog.FullName -ForegroundColor Yellow
     }
     Catch{
        Write-Host "Error deleting: $iisLog.FullName; Error Message: $($_.Exception.Message)" -ForegroundColor Cyan
     }
    }
}
 
 
# Delete content of C:\Windows\SoftwareDistribution\Download, C:\Windows\Temp,C:\Windows\ccmcache and user temp folders
 
 
 foreach ($folderToCheck in $foldersToCheck){
     If (Test-Path -Path $folderToCheck){
     if([bool](Check-if-empty-folder -folderName $folderToCheck) -eq 1 ){
         $folderList = Get-ChildItem -Path $folderToCheck
         foreach($folder in $folderList){
         Try{
            Remove-Item $folder.FullName -Recurse -Force  -ErrorAction Stop
            Write-Host "Deleted: " $folder.FullName -ForegroundColor Yellow
         }
         Catch{
           Write-Host "Error deleting: $folder.FullName; Error Message: $($_.Exception.Message)" -ForegroundColor Cyan
      }
     }
   }
 }
}
  
# Delete unknown user profiles
 
Get-CimInstance win32_userprofile | foreach {
    ""
    $u = $_          # save our user to delete later
    try {
        $objSID = New-Object System.Security.Principal.SecurityIdentifier($U.sid) -ErrorAction stop
        $objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
        "User={0}" -f $objUser.Value
        "Path={0}" -f $u.LocalPath
        "SID={0}" -f $u.SID
    }
    catch {
        "!!!!!Account Unknown!!!!!"
        "Path={0}" -f $u.LocalPath
        "SID={0}" -f $u.SID
        Remove-CimInstance -inputobject $u -Verbose
    }
}
 
# Compress WinSxS folder if disk used space is more than 85%
# Get system drive
$systemDrive = get-wmiobject -class win32_logicaldisk | where-object {$_.DeviceID -eq (Get-WmiObject Win32_OperatingSystem).SystemDrive}
# Get system drive used space in percents
$usedSpace = $systemDrive.Size - $systemDrive.FreeSpace
$usedpersentage = $usedSpace/$systemDrive.Size*100
$usedpersentage = [math]::Round($usedpersentage,2)
  
# If disk usage is more than 85%, compress WinSxS folder
if ($usedpersentage -gt 85){
   Dism.exe /Online /Cleanup-Image /StartComponentCleanup
}