I have app pools on the IIS web server with settings that I have been able to get out in a CSV file but I have a need to change some settings based on that CSV file extracted previously.
How do I configure the settings with powershell scripts without going from server to server? Here is a sneak peak of the script I have written:
$IISInstalled = (Get-WindowsFeature -Name "Web-Server").installed
If ($IISInstalled -ieq $false){
Write-Output "IIS not installed"
exit
}
Import-Module webadministration
$hostname = hostname
$allapppools = (Set-Item IIS:\AppPools\).name
$path = "c:\appsettings\"
if(!(test-path $path))
{
New-Item -ItemType Directory -Force -path $path
}
$allapppools | ForEach-Object($_) {
$apppool=Set-ItemProperty IIS:\AppPools\$_
$obj = New-Object PSObject
$obj | Add-Member Hostname $hostname
$obj | Add-Member AppPoolName $apppool.Name
$obj | Add-Member .NETCLRVer $apppool.ManagedRuntimeVersion
$obj | Add-Member 32-bit $apppool.Enable32BitAppOnWin64
$obj | Add-Member ManagedPipeline $apppool.ManagedPipelineMode
$obj | Add-Member QueueLength $apppool.QueueLength
$obj | Add-Member StartupMode $apppool.StartMode
$obj | Add-Member recycling-periodicRestarttime $apppool -Name recycling.periodicRestart.time -Value '0.23:00:00'
$obj | Add-Member recycling-periodicRestartHours $apppool -Name recycling.periodicRestart.time.Hours -Value '23'
$obj | Add-Member recycling-periodicRestartDays $apppool -Name recycling.periodicRestart.time.Days -Value '0'
$obj | Add-Member recycling-periodicRestartMinutes $apppool -Name recycling.periodicRestart.time.Minutes -Value '0'
$obj | Add-Member recycling-periodicRestartTotalHours $apppool -Name recycling.periodicRestart.time.TotalHours -Value '23'
$obj | Add-Member recycling-periodicRestartTotalMinutes $apppool -Name recycling.periodicRestart.time.TotalMinutes -Value '1380'
$obj | Add-Member recycling-periodicRestartTotalDays $apppool -Name recycling.periodicRestart.time.TotalDays -Value '0.958333'
$obj | Add-Member ItemXPath $apppool.ItemXPath
$obj | Add-Member PSPath $apppool.PSPath
$obj | Add-Member PSParentPath $apppool.PSParentPath
$obj | Add-Member PSChildName $apppool.PSChildName
$obj | Add-Member PSDrive-Root $apppool.PSDrive.Root
$obj | export-csv \\w2-cmn-util02\Reports\app-pool-azure-20210729.csv -NoTypeInformation -append
}
I understand I might have to change the scripts to effect changes on the server but I do not know how this should be done, please I'd need some help. Could someone advice on the working way to make changes on the some pools and not all.
Thanks
Need help with speeding up the script as it is going to be run against 10-20K servers. Currently tested on 4K servers and took almost 6 hours. Tried running it asjob (One parent job and 4000 childjobs, it runs fine and a lot faster but the parent job gets stuck in "running" state forever. It is because one of the childjobs stays in "Notstarted" state. Not sure how to fix that.
######################################################################################
$today = Get-Date
$path = (Get-Location).Path
$path += "\"
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = $path + "Computers.txt"
$outfile = $path + "Report\" + "Certificate_Report_$date.csv"
$transcript = $path + "Logs\" + "Transcript_$date.log"
Start-Transcript $transcript
$computers = gc $Inputfile
Foreach ($c in $computers){
$cert = Invoke-Command -ComputerName $c -ScriptBlock{Get-ChildItem Cert:\localmachine -Recurse} -ErrorVariable issue -ErrorAction Continue
If ($issue){
$Connection = $Error[0].FullyQualifiedErrorId
$obj1 = New-Object Psobject
$Obj1 | Add-Member -MemberType NoteProperty -Name Server -Value $c
$Obj1 | Add-Member -MemberType NoteProperty -Name Serverconnection -Value $Connection
#$report += $obj1
$obj1 | Export-Csv $outfile -NoTypeInformation -Append -force
}
Else{$Connection = "Success"}
Foreach ($cer in $cert){
if($cer.Thumbprint -ne $null){
$obj = New-Object Psobject
$Obj | Add-Member -MemberType NoteProperty -Name Server -Value $c
$Obj | Add-Member -MemberType NoteProperty -Name Serverconnection -Value $Connection
$Obj | Add-Member -MemberType NoteProperty -Name PsParentpath -Value $Cer.PsParentpath
$Obj | Add-Member -MemberType NoteProperty -Name Subject -Value $Cer.Subject
$Obj | Add-Member -MemberType NoteProperty -Name Thumbprint -Value $Cer.Thumbprint
$Obj | Add-Member -MemberType NoteProperty -Name DnsNamelist -Value $Cer.DNSNamelist
$Obj | Add-Member -MemberType NoteProperty -Name FriendlyName -Value $Cer.FriendlyName
$Obj | Add-Member -MemberType NoteProperty -Name Issuer -Value $Cer.Issuer
$Obj | Add-Member -MemberType NoteProperty -Name Valid_From -Value $Cer.NotBefore
$Obj | Add-Member -MemberType NoteProperty -Name Expiration_Date -Value $Cer.NotAfter
if ($cer.NotAfter -lt $today){
$status = "Expired"
}
Else{$status = "Valid"}
$Obj | Add-Member -MemberType NoteProperty -Name Cert_Status -Value $status
$obj | Export-Csv $outfile -NoTypeInformation -Append
}
}
}
Stop-Transcript
The only obvious optimization that comes to mind (without parallelizing the remote queries) is to avoid | Add-Member and use [pscustomobject] syntax for the result objects:
$today = Get-Date
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = (Resolve-Path "Computers.txt").Path
$outfile = (Resolve-Path "Report\Certificate_Report_$date.csv").Path
$transcript = (Resolve-Path "Logs\Transcript_$date.log").Path
Start-Transcript $transcript
$computers = gc $Inputfile
Foreach ($c in $computers) {
$cert = Invoke-Command -ComputerName $c -ScriptBlock { Get-ChildItem Cert:\localmachine -Recurse } -ErrorVariable issue -ErrorAction Continue
If ($issue) {
$Connection = $Error[0].FullyQualifiedErrorId
$obj1 = [pscustomobject]#{
Server = $c
Serverconnection = $Connection
} | Export-Csv $outfile -NoTypeInformation -Append -force
}
Else {
$Connection = "Success"
}
Foreach ($cer in $cert) {
if ($null -ne $cer.Thumbprint) {
[pscustomobject]#{
Server = $c
Serverconnection = $Connection
PsParentpath = $Cer.PsParentpath
Subject = $Cer.Subject
Thumbprint = $Cer.Thumbprint
DnsNamelist = $Cer.DNSNamelist
FriendlyName = $Cer.FriendlyName
Issuer = $Cer.Issuer
Valid_From = $Cer.NotBefore
Expiration_Date = $Cer.NotAfter
Cert_Status = if ($cer.NotAfter -lt $today) { "Expired" } else { "Valid" }
} | Export-Csv $outfile -NoTypeInformation -Append
}
}
}
Stop-Transcript
As Lee_Dailey mentions, you might also want to try offloading parallel execution of the remoting commands to Invoke-Command completely, by passing it all the computer names up front:
Invoke-Command -ComputerName $computers -ScriptBlock {Get-ChildItem Cert:\localmachine -Recurse} -ErrorAction Continue |For-EachObject {
# Process the results here
}
If you want help troubleshooting using background jobs, please post the code with which you have problems :)
Script posted in my question took almost 6 hours to do the same thing this modified version does in under 30 mins. Grateful for the help on this post. Final script below:
$today = Get-Date
$date = Get-Date -uformat "%Y%m%d%H%M"
$Inputfile = gc (Resolve-Path "Computers.txt").Path
$outfile = (Resolve-Path "Report\").Path + "Certificate_Report_$date.csv"
$transcript = (Resolve-Path "Logs\").Path + "Transcript_$date.log"
$failed = "Couldn't retrieve Data"
$IC_ScriptBlock = {Get-ChildItem Cert:\localmachine -Recurse}
$IC_Params = #{
ComputerName = $Inputfile
ScriptBlock = $IC_ScriptBlock
ErrorAction = 'SilentlyContinue'
}
$responding = Invoke-Command #IC_Params|ForEach-Object {
if ($null -ne $_.Thumbprint) {
[pscustomobject]#{
Server = $_.pscomputername
PsParentpath = $_.PsParentpath
Subject = $_.Subject
Thumbprint = $_.Thumbprint
DnsNamelist = $_.DNSNamelist
FriendlyName = $_.FriendlyName
Issuer = $_.Issuer
Valid_From = $_.NotBefore
Expiration_Date = $_.NotAfter
Cert_Status = if ($_.NotAfter -lt $today) { "Expired" } else { "Valid" }
} | Export-Csv $outfile -NoTypeInformation -Append
}
}
$not_responding = $Inputfile.Where({
$_ -notin $responding.Pscomputername -and "[$_]" -notin $responding.pscomputername
}).
foreach({
[pscustomobject]#{
Server = $_
PsParentpath = $failed
Subject = $failed
Thumbprint = $failed
DnsNamelist = $failed
FriendlyName = $failed
Issuer = $failed
Valid_From = $failed
Expiration_Date = $failed
Cert_Status = "NA"
} | Export-Csv $outfile -Append -NoTypeInformation
})
when calling the function below, it doesn't display output on "Windows PowerShell console" but on "Windows Powershell ISE" can.
Function DNSSearchOrders{
$Computer = $env:computername
$Networks = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE -ComputerName $Computer
$DNSServers = $Networks.DNSServerSearchOrder
$NetworkName = $Networks.Description
$PrimaryDNSServer = $DNSServers[0]
$SecondaryDNSServer = $DNSServers[1]
$TertiaryDNSServer = $DNSServers[2]
$OutputObj = #()
$NewObj = New-Object -Type PSObject
$NewObj | Add-Member -MemberType NoteProperty -Name Domain -Value $Computer.ToUpper()
$NewObj | Add-Member -MemberType NoteProperty -Name PrimaryDNSServers -Value $PrimaryDNSServer
$NewObj | Add-Member -MemberType NoteProperty -Name SecondaryDNSServers -Value $SecondaryDNSServer
$NewObj | Add-Member -MemberType NoteProperty -Name TertiaryDNSServers -Value $TertiaryDNSServer
$OutputObj += $NewObj
$NewObj = New-Object -Type PSObject
$NewObj | Add-Member -MemberType NoteProperty -Name Domain -Value contoso.com
$NewObj | Add-Member -MemberType NoteProperty -Name PrimaryDNSServers -Value 111.111.88.88
$NewObj | Add-Member -MemberType NoteProperty -Name SecondaryDNSServers -Value 222.333.444.88
$NewObj| Add-Member -MemberType NoteProperty -Name TertiaryDNSServers -Value "DC IP Address"
$OutputObj += $NewObj
$OutputObj
}
DNSSearchOrders
I've got 2 Scheduled Tasks in Windows: StartAppPool and StopAppPool.
In StartAppPool I've got only 1 job trigger. In StopAppPool I've got 2 job triggers.
I'm trying to create a script that can display the status of my Scheduled Tasks and related properties.
Write-Host "9. Checking Task Scheduler Execution Status...." -BackgroundColor DarkCyan
$taskService = New-Object -ComObject "Schedule.Service"
$taskService.Connect($env:COMPUTERNAME)
$rootTaskFolder = $taskService.GetFolder('\')
$tasks = $rootTaskFolder.GetTasks(1) | Where-Object { $_.Name -in 'StopAppPool','StartAppPool' }
$TaskArray = #()
ForEach ( $task in $tasks ) {
$object = New-Object -TypeName PSObject
$object | Add-Member -Name 'Name' -MemberType Noteproperty -Value $task.Name
$object | Add-Member -Name 'TaskEnabled' -MemberType Noteproperty -Value $task.Enabled
$object | Add-Member -Name 'LastRunTime' -MemberType Noteproperty -Value $task.LastRunTime
$object | Add-Member -Name 'NextRunTime' -MemberType Noteproperty -Value $task.NextRunTime
$TaskXMLObject = [xml]$task.Xml
$CalendarTriggers = $TaskXMLObject.Task.Triggers.CalendarTrigger
$TaskTriggerArray = #()
ForEach ($CalendarTrigger in $CalendarTriggers) {
$object2 = New-Object -TypeName PSObject
$object2 | Add-Member -Name 'StartBoundary' -MemberType Noteproperty -Value ( Get-Date $CalendarTrigger.StartBoundary.Replace('T',' ') -Format "dd/MM/yyyy hh:mm:ss tt" )
$object2 | Add-Member -Name 'Enabled' -MemberType Noteproperty -Value $CalendarTrigger.Enabled
$DaysOfWeek = ( $CalendarTrigger.ScheduleByWeek.DaysOfWeek | Get-Member -MemberType Property | Select -ExpandProperty Name) | ForEach-Object -Process { [enum]::parse([System.DayOfWeek],$_ ) } | Sort-Object #parsing the values into an enum will allow the objects to be sorted by day instead of alphabetical order
$object2 | Add-Member -Name 'DaysOfWeek' -MemberType Noteproperty -Value ( $DaysOfWeek -join ', ' )
$TaskTriggerArray += $object2
}
$object | Add-Member -Name 'StartBoundary' -MemberType NoteProperty -Value ($TaskTriggerArray.StartBoundary | Out-String )
$object | Add-Member -Name 'TriggerEnabled' -MemberType Noteproperty -Value ($TaskTriggerArray.Enabled | Out-String )
$object | Add-Member -Name 'DaysOfWeek' -MemberType Noteproperty -Value ($TaskTriggerArray.DaysOfWeek | Out-String )
$TaskArray += $object
}
$TaskArray | Format-Table Name, TaskEnabled, LastRunTime, NextRunTime, #{Label='StartBoundary';Expression={(($_.StartBoundary )}}, TriggerEnabled, DaysOfWeek -Wrap
Unfortunately, the "StartBoundary" column seems to append extra spaces behind and I couldn't figure how how to fix that. Here's the sample output
Name TaskEnabled LastRunTime NextRunTime StartBoundary TriggerEnabled DaysOfWeek
---- ----------- ----------- ----------- ------------- -------------- ----------
StartAppPool True 10/2/2017 6:00:00 AM 11/2/2017 6:00:00 AM 29/09/2016 06:00:00 AM true Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
StopAppPool True 10/2/2017 5:10:01 AM 11/2/2017 5:10:00 AM 29/09/2016 05:10:00 AM true Tuesday, Wednesday, Thursday, Friday, Saturday
05/11/2016 10:00:00 AM true Saturday
Anyone has an idea how I can fix that?
Try to use Trim method, like this:
$_.StartBoundary.ToString().Trim()
You can also try to add -AutoSize switch for Format-Table cmdlet
I found a possible solution to the question here: http://youtu.be/71Vc9QiraQE
I am not a power shell user myself and cannot quite follow the code in the script.
It would be a useful script to help with reinstalls when operating systems have been upgraded. My problem is I would like to understand it more before using it or at least be told it looks harmless.
Here is the Script:
function Get-WindowsKey {
## function to retrieve the Windows Product Key from any PC
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
## decrypt base24 encoded binary data
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
}
Get-WindowsKey
The first command used in Windows PowerShell:
Set-ExecutionPolicy RemoteSigned
If the first script doesn't work, try this:
I was able to get my key using this, but I had to fix the script, changing the last line to:
function Get-WindowsKey {
# ...
}
Get-WindowsKey localhost
I think the YouTube video overdoes it a bit, you could simply cut and paste the contents of that into a PowerShell window without having to muck about with executionpolicy and importing the file.
Just open PowerShell, paste the below code into it and hit Enter a few times until it returns.
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId4"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
## decrypt base24 encoded binary data to characters.
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
I highly recommend backing up the hard drive (or replacing it entirely) in case the script hasn't decrypted it properly. It would kind of suck to go through all this to find it's wrong and you've just wiped your previous OS.
Trying to explain what the code does.
The script is reading the registry value Software\Microsoft\Windows NT\CurrentVersion\DigitalProductId and extracts the relevant part
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
As the comment in the code says, it then decodes ("decrypts") the value in the for loop. The registry value is base24 encoded.
$charsArray = "B","C","D","E","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","5","6","7","8","9"
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
And finally the code shows the product key along with several system values
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
Depending on whether it's a retail or enterprise version, the registry value may be found in either DigitalProductId or DigitalProductId4, e.g.
$regValue = "DigitalProductId"
vs
$regValue = "DigitalProductId4"
The code is wrong, you are getting the key decrypted wrong...
Look at $charsArray, no E and a couple others...
function Get-WindowsKey {
## function to retrieve the Windows Product Key from any PC
param ($targets = ".")
$hklm = 2147483650
$regPath = "Software\Microsoft\Windows NT\CurrentVersion"
$regValue = "DigitalProductId"
Foreach ($target in $targets) {
$productKey = $null
$win32os = $null
$wmi = [WMIClass]"\\$target\root\default:stdRegProv"
$data = $wmi.GetBinaryValue($hklm,$regPath,$regValue)
$binArray = ($data.uValue)[52..66]
$charsArray = "B","C","D","F","G","H","J","K","M","P","Q","R","T","V","W","X","Y","2","3","4","6","7","8","9"
## decrypt base24 encoded binary data
For ($i = 24; $i -ge 0; $i--) {
$k = 0
For ($j = 14; $j -ge 0; $j--) {
$k = $k * 256 -bxor $binArray[$j]
$binArray[$j] = [math]::truncate($k / 24)
$k = $k % 24
}
$productKey = $charsArray[$k] + $productKey
If (($i % 5 -eq 0) -and ($i -ne 0)) {
$productKey = "-" + $productKey
}
}
$win32os = Get-WmiObject Win32_OperatingSystem -computer $target
$obj = New-Object Object
$obj | Add-Member Noteproperty Computer -value $target
$obj | Add-Member Noteproperty Caption -value $win32os.Caption
$obj | Add-Member Noteproperty CSDVersion -value $win32os.CSDVersion
$obj | Add-Member Noteproperty OSArch -value $win32os.OSArchitecture
$obj | Add-Member Noteproperty BuildNumber -value $win32os.BuildNumber
$obj | Add-Member Noteproperty RegisteredTo -value $win32os.RegisteredUser
$obj | Add-Member Noteproperty ProductID -value $win32os.SerialNumber
$obj | Add-Member Noteproperty ProductKey -value $productkey
$obj
}
}
$key = Get-WindowsKey
$key.productkey