Sunday, May 28, 2023

2023-05-27

Automatic 1111 Stable Diffusion web UI

New installation

Webui version: v1.3.0
Python: 3.10.9
Torch: 2.0.1+cu118
Xformers: N/A (python 2.0 replace xformers completly)
Gradio: 3.31.0

(torch 2 is approximatly twice the speed on gtx 4080 for image generation)

Installation

1
Install GIT (add to path)

2
Install Python for windows

3
Make a new folder to install the web ui in





Go into the new folder

(windows 11)
Left click inside folder to open a prompt



Type this command in the window:
Git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git .


Press enter, wait for it to finish

inside the folder you created, Execute:
webui-user.bat
Wait for the downloads to finish (3.97 gig is the longest gig)


Just after the last big download, you will see a link to open the web ui:




In windows 11 you can just CTRL click the link to open it:
http://127.0.0.1:7860

The webui is installed, but not very fast

1st generation:
masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 3011927377, Size: 512x512, Model hash: 6ce0161689, Model: v1-5-pruned-emaonly, Denoising strength: 0.7, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent, Version: v1.3.0

Time taken: 6.77s Torch active/reserved: 4265/5340 MiB, Sys VRAM: 7772/16376 MiB (47.46%)

4
Optimisation
We can do much better than that on a gtx 4080

Close the webui
Close the dos windows with the python stuff in it


Edit the file:

Change this line: (tested only on gtx 4080)
Set COMMANDLINE_ARGS=  --opt-sdp-attention --opt-channelslast --no-half-vae
Add this line:
set safetensors_fast_gpu=1
You can also add this line to update the ui each time you run it:
git pull


Reopen thewebui

 

Get last generation parameters:



Now on my old setup, this was the time (but i was already on torch 2.0, i updated it manually)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 3011927377, Size: 512x512, Model hash: 6ce0161689, Model: v1-5-pruned-emaonly, Denoising strength: 0.7, Version: v1.2.1, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent

Time taken: 8.93sTorch active/reserved: 6863/7732 MiB, Sys VRAM: 10164/16376 MiB (62.07%)

So in this new torch 2 version of the webui, addin the 3 parameters on the command line changed nothing in the speed: (it took a little more ram, but one of the parameter do that)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 172152486, Size: 512x512, Model hash: 6ce0161689, Model: v1-5-pruned-emaonly, Denoising strength: 0.7, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent, Version: v1.3.0

Time taken: 7.69s Torch active/reserved: 6855/7718 MiB, Sys VRAM: 10142/16376 MiB (61.93%)

Now for DPM++ (all version i think)
It is broken with torch 2.0, but only in this version, my precedent version was fine. (even with torch 2.0.0)
Broken = very ugly images, even at 100 sampling steps


Now with juggernaut_v18.safetensors [3f0f3a35e2]
Euler A


There ia blurry edge on the image, but not everywhere

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 100, Sampler: Euler a, CFG scale: 7, Seed: 172152486, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent, Version: v1.3.0

Time taken: 10.63sTorch active/reserved: 6864/7742 MiB, Sys VRAM: 10182/16376 MiB (62.18%)

So i removed my presonalisations in the webui-user.bat, same result







masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 100, Sampler: Euler a, CFG scale: 7, Seed: 172152486, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent, Version: v1.3.0

Time taken: 9.74sTorch active/reserved: 4257/5320 MiB, Sys VRAM: 7744/16376 MiB (47.29%)

A little faster tho, so i do not need the folowing personalisations in the webui-user.bat:
Delete parameters, even the one for fast gpu: (it is probably integrated in the ui parameters now)

It a little faster and take less ram without the personalisations i was using when i implemented torch 2.0.0 myself in the old webui.

But a new problem arised:
Blurry edges around the characters body, strangly, not the face (100 sampling step)

Testing random seeds now... 

Model (H:\sdwebui\models\Stable-diffusion, YAML needed))
juggernaut_v18.safetensors [3f0f3a35e2])
juggernaut_v18.yaml

One image on 3 had the blur:




(3 images batch count)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Steps: 100, Sampler: Euler a, CFG scale: 7, Seed: 3358036764, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: Latent, Version: v1.3.0

Time taken: 29.24s Torch active/reserved: 4257/5320 MiB, Sys VRAM: 7744/16376 MiB (47.29%)

I removed some words in promt and addes negative prompts
No change, still some blurs in costume:



DPM++ SDE Karras not really working:
(even the face have blurs)



Now i noticed it was the Hires. fix breaking things (DPM++ with latent hires fix)
hires fix: ESRGAN 4x was ok
hires fix: latent: broken with DPM++


Next image (30 steps) was ok:

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet
Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7, Seed: 3358036764, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 13.46sTorch active/reserved: 4575/5438 MiB, Sys VRAM: 7890/16376 MiB (48.18%)



I did 6 images to be sure: (in batch count)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet
Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7, Seed: 3358036764, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 1m 21.66sTorch active/reserved: 4581/5448 MiB, Sys VRAM: 7900/16376 MiB (48.24%)



3 images batch count time: (DPM++ take a lot of ram)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet

Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7, Seed: 1170825575, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 41.59s Torch active/reserved: 4570/14874 MiB, Sys VRAM: 16376/16376 MiB (100.0%)

3 images batch size:

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet

Steps: 30, Sampler: DPM++ SDE Karras, CFG scale: 7, Seed: 1177518657, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 38.80s Torch active/reserved: 9525/10216 MiB, Sys VRAM: 12668/16376 MiB (77.36%)

Just a little faster

3 images batch size Euler a: (much faster)
(note that it is hires 1024 x 1024, 7 sec / image in euler a 512 x 512)

masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet

Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 3422560359, Size: 512x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, Hires upscale: 2, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 21.84s Torch active/reserved: 9560/10252 MiB, Sys VRAM: 12874/16376 MiB (78.62%)


Installing controlnet
sd-webui-controlnet manipulations
After: close webui, close python (dos window) then restart


Controlnet come with 0 models by default:


Download them from here: (or internet)
https://huggingface.co/lllyasviel/ControlNet-v1-1/tree/main

Control net was ok, and canny see the hair color now? Is this new?



Now:
And:

Enabled controlnet
Latent couple
Composable lora
APPLY

Prompt:


floor
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, jennifer lawrence wearing widowmaker costume from overwatch

Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet
Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 1340533486, Size: 600x512, Model hash: 3f0f3a35e2, Model: juggernaut_v18, Denoising strength: 0.35, ControlNet: "preprocessor: canny, model: control_v11p_sd15_canny [d14c016b], weight: 1, starting/ending: (0, 1), resize mode: Crop and Resize, pixel perfect: False, control mode: Balanced, preprocessor params: (512, 100, 200)", Hires upscale: 1.7, Hires steps: 25, Hires upscaler: ESRGAN_4x, Version: v1.3.0

Time taken: 47.01sTorch active/reserved: 5447/8366 MiB, Sys VRAM: 10806/16376 MiB (65.99%)

Latent couple still not working, even after 6 images: (it merge the tow faces)
(controlnet to canny)


Prompt for composable lora latent couple:

floor
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, woman miley cyrus wearing tracer costume from overwatch
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, man steeve rogers as captain america

Tried Depth:


Depth gave ma a captain america, but female:


Open pose:


Captain is still a female:


scribble:


Still no captain america male:


Checked this in setting, not working:

2 control net models
Allow script to control controlnet extension

Loaded defautl 1.5 model:

More ugly, but captain is still not a male:



Changed to DPM++ 2m SDE karas
no captain yet


vérified my scribble, look ok:



Delete folder in extensions there: 

Installed this version:
https://github.com/SoCuteShibe/stable-diffusion-webui-two-shot.git

Extension in webui
Install from url

Captain is still a woman: (but the costume is there...)


new prompt:

floor
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, steeve rogers as captain america

Still:



Very simple prompt:

floor
AND miley cyrus wearing tracer costume from overwatch
AND steeve rogers as captain america

Openpose (no female or male i presume in open pose)

Partial work!



So back to complicated prompt:
with openpose:

floor
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, jennifer lawrence wearing widowmaker costume from overwatch

Changed to euler a
No hiresfix
control net on
latent couple on
composable lora on

Prompt...:
floor
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, miley cyrus wearing tracer costume from overwatch
AND masterpiece, best quality, (high resolution:1.6), 8k, 2k, 4k, 16k, (open pores, skin imperfections), insane details, sharp focus, grain:0.5, perfect proportions, rule of thirds, hayden pennetiere wearing widowmaker costume from overwatch


Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet
Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 2700467051, Size: 1360x1024, Model hash: 3f0f3a35e2, Model: juggernaut_v18, ControlNet 0: "preprocessor: openpose_full, model: control_v11p_sd15_openpose [cab727d4], weight: 1, starting/ending: (0, 1), resize mode: Crop and Resize, pixel perfect: False, control mode: Balanced, preprocessor params: (512, 100, 200)", Version: v1.3.0

Time taken: 50.86sTorch active/reserved: 7832/11774 MiB, Sys VRAM: 14397/16376 MiB (87.92%)

Result...:


It's still a mix of Hayden and Miley, not two separate person
But hey, not bad for no hires fix, this model is amazing: juggernaut_v18.safetensors [3f0f3a35e2] (of course this is a controlnet with weight still)

I still have the costumes, not the face:


floor
AND miley cyrus wearing tracer costume
AND hayden pennetiere wearing widowmaker costume


Negative prompt: child, painting, drawing, sketch, cartoon, anime, render, blurry, deformed, disfigured, morbid, mutated, bad anatomy, bad art, disembodied limb, disembodied hand, portrait, floating hand, floating leg, floating arm, telephoto lens, dislocated limb, sprain, fracture, bad theet
Steps: 30, Sampler: Euler a, CFG scale: 7, Seed: 3709915190, Size: 1360x1024, Model hash: 3f0f3a35e2, Model: juggernaut_v18, ControlNet 0: "preprocessor: openpose_full, model: control_v11p_sd15_openpose [cab727d4], weight: 1, starting/ending: (0, 1), resize mode: Crop and Resize, pixel perfect: False, control mode: Balanced, preprocessor params: (512, 100, 200)", Version: v1.3.0

Time taken: 51.09sTorch active/reserved: 7832/11774 MiB, Sys VRAM: 14415/16376 MiB (88.03%)

-----

Tried with parameters in the command line:
floor 
AND miley cyrus
AND hayden pennetiere
Latent Couple: "divisions=1:1,1:2,1:2 positions=0:0,0:0,0:1 weights=0.2,0.8,0.8 end at step=20"

not working

-----
Latent couple two shot
Tried with masks instead of controlnet with openpose
Not working either









Thursday, January 12, 2023

KB5022286 january 2023 update windows server 2019 hyper-v

KB5022286 january 2023 update windows server 2019 hyper-v

server Dell Inc. PowerEdge R6415

AMD EPYC 7351P 16-Core Processor (32 virtual) (X64)


After this update, hyper-v server started to slow on all vms

No cpu was used, but everything was still slow or freezing

rebooting the host hyper-v or a vm did not resolve the problem (did not reboot all vms, as rebooting one took 40 minutes)


Had to remove the update with this:

DISM /online /get-packages



dism /Online /Remove-Package /PackageName:Package_for_RollupFix~31bf3856ad364e35~amd64~~17763.3887.1.7

reboot (20 minutes)

Sunday, September 25, 2022

Powershell model with everything

 Powershell model with everything

Hello,

This model is to serve as reference for many powershell functions. Parameters, usefull functions, strings manipulation, psobject, xml, html.


TITLE OF A POWERSHELL SCRIPT

######################################################################

$title01 = "update all vm, get vm properties (vmware and hyperv)"

######################################################################

cls


PARAMETERS

################################################

# parameters

################################################

$params01 = New-Object PsObject

$params01 | Add-Member NoteProperty -Name domain_computers_scan -value 0        # scan all computers in the domain

$params01 | Add-Member NoteProperty -Name domain_computers_scan1pc -value ""        # scan one computer in the domain (if empty, scan all enabled win10 win11)


REQUIREMENTS

#############################################
# requis
#############################################
# windows 7
#   Set-ExecutionPolicy RemoteSigned
# windows 10
#   Set-ExecutionPolicy -scope currentuser RemoteSigned
# when module is there but wont load command
#   Set-ExecutionPolicy -scope currentuser unrestricted


Tuesday, September 28, 2021

POWERSHELL MODULE VERIFY UNINSTALL INSTALL UPGRADE

I was tired of managing multiple versions of modules installed in my powershell

When multiple version of a module are installed, powershell sometimes call the wrong version of the module and end up with a command not found in the old version.

Here is a function to Verify, uninstall, install and upgrade powershell modules:


##################################################

# module verify, update or get

##################################################

function moduleverifgetupdate

{

    param($modulearr01, $versionminimumarr01, $versionupgradetolatestarr01, $verbosesub01)


    function moduleverify 

    {

        param($module01, $verbosesub01)

        

        $modules02 = Get-Module $module01 -ListAvailable | Select-Object Name,Version | Sort-Object Version

        

        $found01 = 0

        $foundsameversion01 = 0


        foreach($module02 in $modules02)

        {

            ### find module in installed modules

            if($module02.name -eq $module01)

            {

                $found01++

                $msg01 = "Module found...: " + $module02.name + "   " + $module02.version.tostring()

                if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}


                ### check version

                if($module02.version -eq $versionminimum01)

                {

                    # uninstall cause it's not the right version

                    $foundsameversion01 = 1

                }

            }

        }

        return $found01, $foundsameversion01, $modules02

    }


    #$module = Get-Module AzureIoT* -ListAvailable | Select-Object Name,Version | Sort-Object Version

    

    for($i=0; $i -lt $modulearr01.count; $i++)

    {

        $module01 = $modulearr01[$i]

        $versionminimum01 = $versionminimumarr01[$i]

        $versionupgradetolatest01 = $versionupgradetolatestarr01[$i]

        

        $msg01 = "Module requis to find...: " + $module01 + "   " + $versionminimum01.tostring()

        if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}


        $found01, $foundsameversion01, $modules02 = moduleverify $module01 $verbosesub01


        #write-host $found01 $foundsameversion01


        # found or not found or found same version or foudn a different version

        $msg01 = "found01...: $found01   Foundsameversion01...: $foundsameversion01"

        if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}


        # manage if found and if same version


        # if more than one module with same name is found, we flush them all

        if($found01 -gt 1)

        {

            $msg01 = "Module found MULTIPLE VERSIONS, uninstalling bad versions..."

            if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}            


            for($i2=0; $i2 -lt $modules02.count; $i2++)

            {

                $modulename02 = $modules02[$i2].name

                $versionminimum02 = $modules02[$i2].version


                ### find module in installed modules

                if($modulename02 -eq $module01)

                {

                    ### check version

                    if($versionminimum02 -ne $versionminimum01)

                    {

                        $msg01 = "Module found to ininstall...: " + $modulename02 + "   " + $versionminimum02.tostring()

                        if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}


                        # uninstall cause it's not the right version

                        Uninstall-Module $module01 -requiredversion $versionminimum02 -force

                    }

                }

            }

        }


        # install module if not found

        if($found01 -eq 0)

        {

            install-module $module01 -requiredversion $versionminimum01 -force

        }


        # upgrade module to the right version (dunno if this will install back versions)

        if(($found01 -eq 1) -and ($foundsameversion01 -eq 0) -and ($versionupgradetolatest01 -eq $true))

        {

            update-module $module01 -force -RequiredVersion $versionminimum01

        }


        $found01, $foundsameversion01, $modules02 = moduleverify $module01 $verbosesub01

        

        $msg01 = "found01...: $found01   Foundsameversion01...: $foundsameversion01"

        if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor Yellow}


        if(($found01 -eq 1) -and ($foundsameversion01 -eq 1))

        {

            $msg01 = "Module installed...: " + $module01 + "   " + $versionminimum01.tostring()

            if($verbosesub01 -eq 1){write-host $msg01 -ForegroundColor green}

        }

        else

        {

            Write-Error $_

        }


        #$msg01 = $modules01.version

        #Write-Host $msg01 -ForegroundColor Yellow


    } # module wanted array


} # function



###############################################################################################

# main

###############################################################################################

# param modules to install or upgrade or replace

[string[]]$modulearr01 = "AzureIoT"

[string[]]$versionminimumarr01 = "1.0.0.5"

[string[]]$versionupgradetolatestarr01 = $false

#[string[]]$modulearr01+= "module22"

#[string[]]$requiredversionarr01+= "2"

#[string[]]$versionupgradetolatestarr01+= $false


# test

$verbosesub01 = 1

#$dummy = moduleverifgetupdate $modulearr01 $versionminimumarr01 $versionupgradetolatestarr01 $verbosesub01


try

{

    $dummy = moduleverifgetupdate $modulearr01 $versionminimumarr01 $versionupgradetolatestarr01 $verbosesub01

}

catch

{

    write-host "ERROR module install   $_"-ForegroundColor Red

    break all

}


$testcommands01 = 

@"

# fake install to check for double when installing

install-module azureiot -requiredversion "1.0.0.1" -force

Get-Module "azureiot" -ListAvailable | Select-Object Name,Version | Sort-Object Version

"@


Friday, May 8, 2020

Dear azure devops team 2020-05-08


Dear azure devops team,

I would like to report a bug

In the web page for azure devops, everytime I click on a project name, the name move at the top of the list and move all the other items down the list.

It cannot be a feature. No way. Somewhere, someone is laughting very hard.

Or it might also be very serious...

Was it was made by "10 second Tom" from the movie "50 first dates"?

If it is the case I forgive him.

If not, if you ever meet the guy who made every clicked project move to the top of the menu, will you please nickname him: "10 seconds Tom" for me?


Monday, March 9, 2020

powershell recursive html report with jagged array of psobjects

Recursive html report (made with jagged array of psobject)

When you print, header and footer will repeat each page

Requis: google chrome (for printing)

At the moment it scan for windows code and office code and put it in the report


#########################################################################
$title01 = "report psobject html xml jagged recursive colored"
#########################################################################
cls
# this recursive script will:
# take a psobject with sub psobjects that contain data about a report
# put the main psobject in a html table, all sub psobject are replaced with a text
# color any html cell we want to warn about certains things (errors, warnings) in main html/xml hybrid object and all sub html/xml objects
# convert back the main html/xml table into an html report
# insert back all sub html/xml tables (once they were colored)
# generate final html report, printable in google chrome only, with a table format and sheet format compatible for print with header and footer

#########################
# parameters
#########################
# scan office keys
#$computers01 = @("mav03", "mav06", "mav09", "mav10", "mav14", "mav16", "mav18", "mav19")
$computers_local01 = @("localhost","caca")

###################################
# requis
###################################
### Enable-PSRemoting

#powershell.exe -command Enable-PSRemoting -SkipNetworkProfileCheck -force
#Enable-PSRemoting -SkipNetworkProfileCheck -force

#Test-WSMan -ComputerName <IP or host name>

# on HOST ########################################## tooo!
# Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*'

#############################################################################
# color for html report if this string is found, html td color is changed
#############################################################################
# possible colors are defined in $style01
[string[]]$error_message_arr = "WARNING"
[string[]]$error_color_arr = "yellow" # yellow
[string[]]$error_message_Arr+= "ERROR"
[string[]]$error_color_arr+= "red" # red
[string[]]$error_message_arr += "cacasss"
[string[]]$error_color_arr += "yellow" # yellow
[string[]]$error_message_arr += "00:00:00"
[string[]]$error_color_arr += "yellow" # yellow
[string[]]$error_message_arr += "inprogress"
[string[]]$error_color_arr += "yellow" # yellow
[string[]]$error_message_arr += "pending"
[string[]]$error_color_arr += "yellow" # yellow
[string[]]$error_message_Arr+= "failed"
[string[]]$error_color_arr+= "red" # red
[string[]]$error_message_Arr+= "true"
[string[]]$error_color_arr+= "red" # red
[string[]]$error_message_Arr+= "unknow"
[string[]]$error_color_arr+= "red" # red

###############################
# error management object
###############################
$error01 = New-Object PsObject
$error01 | Add-Member NoteProperty -Name number -value ''
$error01 | Add-Member NoteProperty -Name description -value ''
$error01 | Add-Member NoteProperty -Name error02 -value ''

#######################
# logfile
#######################
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$scriptname = split-path -leaf $MyInvocation.MyCommand.Definition
$Logfilename = $scriptname + "_log.txt"
$logfile = $scriptPath + "\" + $Logfilename
$logall = 1

$msg01 = "START " + $title01
write-host $msg01 -ForegroundColor Green
if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile}catch{}}

#######################################################################
# get domain name to check for hyper-v or esx vmware on each client
#######################################################################
# (Get-WmiObject Win32_ComputerSystem).Domain
$domain01 = (Get-WmiObject Win32_ComputerSystem).Domain

#################################
# function color xml html table
#################################

function color_xml_table([xml]$ttable)
{
    for($trcnt02=1; $trcnt02 -le $ttable.table.tr.count; $trcnt02++)
    {
        for($tdcnt02=1; $tdcnt02 -le $ttable.table.tr[$trcnt02].td.Count; $tdcnt02++)
        {
            #write-host "td" $ttable.table.tr[$trcnt02].td.Count
           
            # having 1 item reverse y and x in a table
            if($ttable.table.tr[$trcnt02].td.Count -eq 1)
            {
                # cannot index element if there is only one
                $item03 = $ttable.table.tr[$trcnt02].td
            }
            else
            {
                $item03 = $ttable.table.tr[$trcnt02].td[$tdcnt02]
            }

            for($errorcnt = 0; $errorcnt -lt $error_message_arr.count; $errorcnt++)
            {
                $error_message = $error_message_arr[$errorcnt]
                if($item03 -like "*$error_message*")
                {
                    #write-host "changed color" $error_color_arr[$errorcnt] -ForegroundColor Yellow
                   
                    # having 1 item reverse y and x in a table
                    if($ttable.table.tr[$trcnt02].td.Count -eq 1)
                    {
                        $ttable.table.tr[$trcnt02].ChildNodes.SetAttribute('class',$error_color_arr[$errorcnt])
                    }
                    else
                    {
                        try
                        {
                        $ttable.table.tr[$trcnt02].ChildNodes[$tdcnt02].SetAttribute('class',$error_color_arr[$errorcnt])
                        }
                        catch{}
                                       
                    }
                }
            }
        }
    }
    return $ttable
}

function ConvertTo-ProductKey
{
    <# 
    .SYNOPSIS 
        Converts registry key value to windows product key.
       
    .DESCRIPTION 
        Converts registry key value to windows product key. Specifically the following keys:
            SOFTWARE\Microsoft\Windows NT\CurrentVersion\DigitalProductId
            SOFTWARE\Microsoft\Windows NT\CurrentVersion\DigitalProductId4
       
    .PARAMETER Registry
        Either DigitalProductId or DigitalProductId4 (as described in the description)
       
    .NOTES 
        Author: Zachary Loeber
        Original Author: Boe Prox
        Version: 1.0
         - Took the registry setting retrieval portion from Boe's original script and converted it
           to this basic conversion function. This is to be used in conjunction with my other
           function, get-remoteregistryinformation
   
    .EXAMPLE
     PS > $reg_ProductKey = "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
     PS > $a = Get-RemoteRegistryInformation -Key $reg_ProductKey -AsObject
     PS > ConvertTo-ProductKey $a.DigitalProductId
   
            XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
           
     PS > ConvertTo-ProductKey $a.DigitalProductId4 -x64
   
            XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
       
        Description
        -----------
        Retrieves the product key information from the local machine and converts it to a readible format.
    #>     
    [cmdletbinding()]
    param (
        [parameter(Mandatory=$True,Position=0)]
        $Registry,
        [parameter()]
        [Switch]$x64
    )
    begin {
        $map="BCDFGHJKMPQRTVWXY2346789"
    }
    process {
        $ProductKey = ""

        $prodkey = $Registry[0x34..0x42]

        for ($i = 24; $i -ge 0; $i--)
        {
            $r = 0
            for ($j = 14; $j -ge 0; $j--)
            {
                $r = ($r * 256) -bxor $prodkey[$j]
                $prodkey[$j] = [math]::Floor([double]($r/24))
                $r = $r % 24
            }
            $ProductKey = $map[$r] + $ProductKey
            if (($i % 5) -eq 0 -and $i -ne 0)
            {
                $ProductKey = "-" + $ProductKey
            }
        }
        $ProductKey
    }
}

###############################################
# get all programs installed on the computer
###############################################
function getallprograms01($vm_name01)
{

    $result01 = @()

    $msg01 = "PROGRAM (getting 64 bits programs) pssession opened"
    if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}

    $remote_command01 = "try {Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, @{Name = 'type'; Expression = {`"64 bits`"}} } catch {throw `$_}"
               
    $remote_command_block01 = [scriptblock]::Create($remote_command01)

    $job_program64 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
    $results01 = $job_program64| Wait-Job -timeout $jobtimeout01 | Receive-Job
    $job_program64 | remove-job


    $msg01 = "PROGRAM (getting 32 bits programs) pssession opened"
    if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}

    $remote_command01 = "try {Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, @{Name = 'type'; Expression = {`"32 bits`"}} } catch {throw `$_}"
               
    $remote_command_block01 = [scriptblock]::Create($remote_command01)

    $job_program32 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
    $results01 += $job_program32 | Wait-Job -timeout $jobtimeout01 | Receive-Job
    $job_program32 | remove-job

    return $result01

}

#########################################
# clean up bad caracter in psobject
#########################################

function psobject_cleanup01($psobject01)
{
    #write-host "cleaning"
    # get properties names of this object (columns names)
    $properties = $null
    $properties = $psobject01 | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name

    foreach($line in $psobject01)
    {
        for($elecnt02 = 0; $elecnt02 -lt $properties.count; $elecnt02++)
        {
            # when encountering a replaced sub object, we dont care if there is an error because it's a string that will be replaced later anyway by a table
            try{$line.($properties[$elecnt02]) = $line.($properties[$elecnt02]).tostring()}catch{}
            try{$line.($properties[$elecnt02]) = $line.($properties[$elecnt02]) -replace '[^a-zA-Z0-9 ,.!"/$%?&()_+=-]', ''}catch{}
            $line.($properties[$elecnt02]) = $line.($properties[$elecnt02]) -replace '[^a-zA-Z0-9 ,.!"/$%?&()_+=-]', ''
        }
    }
    return $psobject01
}

function report_object_replacements01
{
    param($object01)
    # will detect object within array
    # substitute them a text
    # at final step, with a xml manipulation, replace all text with a html table so the report can have subtables
   
    $global:recurse_level01 = 0
    $global:item_to_replace_index01 = 0
    $global:replace_arr01 = @()

    #convert to html
    #$object01 = $args[0]

    $file10 = $scriptPath + "\" + $scriptname + ".htm"

    $msg01 = "Type level 0: " + $object01.GetType().Name
    #write-host $msg01
    if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}       
   
    if($object01.GetType().Name -eq "Object[]")
    {
        $object_total = 0
        for($elecnt = 0; $elecnt -lt $object01.count; $elecnt++)
        {
            #$elecnt
       
            $msg01 = ""
            if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}       
       
            if($object01[$elecnt].GetType().Name -eq "Object[]")
            {
                $object_total++
                $msg01 = "Object, calling recurse from main loop"
                write-host $msg01 -ForegroundColor Yellow
                $object01[$elecnt] = report_object_replacements_recursive01($object01[$elecnt])
            }
            else
            {
                #write-host "cleaning: " $object01[$elecnt] -ForegroundColor Magenta
                $object01[$elecnt] = psobject_cleanup01($object01[$elecnt])
                # this is not an object, we do not recurse
                #$msg01 = "not an object"; write-host $msg01 -ForegroundColor Green
            }
        }
       
        #if($object_total -eq 0)
        #{
        #    write-host "cleanup non-recursive"
        #    $object01 = psobject_cleanup01($object01)
        #}
    }
    else
    {
        write-host "ERROR report_html_with_subtables The first param is not an object[]" -ForegroundColor Red
    }


    $htmlfile01 = $object01 | ConvertTo-Html -Head $header
    $htmlfile01 | Out-File $file10
    #write-host "type: " $htmlfile01.GetType().Name
    #write-host $file10
    return $file10

}

function report_object_replacements_recursive01($object02)
{
    $msg01 = "In recursive level: " + $global:recurse_level01
    write-host $msg01
    if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}
   
    if($object02.GetType().Name -eq "Object[]")
    {
        $global:recurse_level01++
        $object_total = 0

        for($elecnt01 = 0; $elecnt01 -lt $object02.count; $elecnt01++)
        {
            if($object02[$elecnt01].GetType().Name -eq "Object[]")
            {
                $object_total++
                $msg01 = "Object, calling recurse from recurse"
                write-host $msg01 -ForegroundColor Yellow
                #$global:replace_arr01+= $object02[$elecnt01]
                $object02[$elecnt01] = report_object_replacements_recursive01($object02[$elecnt01])
                #$object02[$elecnt01] = psobject_cleanup01($object02[$elecnt01])
            }
            else
            {
                #write-host "cleaning recursive: " $object02[$elecnt01] -ForegroundColor Magenta
                $object02[$elecnt01] = psobject_cleanup01($object02[$elecnt01])
                #$msg01 = "not an object"; write-host $msg01 -ForegroundColor Green
                #$object02[$elecnt01] = psobject_cleanup01($object02[$elecnt01])
            }
        }

        $replacement_text01 = "replace with" + $global:item_to_replace_index01
        $replacement01 = New-Object PsObject
        $replacement01 | Add-Member NoteProperty -Name id -value $replacement_text01

        $msg01 = "Replacing sub table with replacement text: " + $replacement_text01
        #write-host $msg01 -ForegroundColor Cyan
        $global:replace_arr01+= ,$object02
        $object02 = $replacement01
        $global:item_to_replace_index01++

        #write-host $object02 -ForegroundColor Magenta
    }
    else
    {
        $global:recurse_level01--
        write-host "not an object RECURSE CALLED FOR NOTHING" -ForegroundColor Green
    }
    return $object02

}

function replace_sub_table_xml($xml02)
{
    for($trcnt=0;$trcnt -lt $xml02.table.tr.count;$trcnt++)
    {
        ################################################################
        # change color if certain keywords are found in html cell
        ################################################################
        for($tdcnt=0;$tdcnt -lt $xml02.table.tr[$trcnt].td.Count;$tdcnt++)
        {
            # cannot index element if there is only one
            if($xml02.table.tr[$trcnt].td.Count -eq 1)
            {
                # cannot index element if there is only one
                $item02 = $xml02.table.tr[$trcnt].td
            }
            else
            {
                $item02 = $xml02.table.tr[$trcnt].td[$tdcnt]
            }


            ####################
            # color item
            ####################
            for($errorcnt = 0; $errorcnt -lt $error_message_arr.count; $errorcnt++)
            {
                $error_message = $error_message_arr[$errorcnt]
                if($item02 -like "*$error_message*")
                {
                    #write-host "changed color" $error_color_arr[$errorcnt] -ForegroundColor Yellow
                   
                    # cannot index element if there is only one
                    if($xml02.table.tr[$trcnt].td.Count -eq 1)
                    {
                        $xml02.table.tr[$trcnt].ChildNodes.SetAttribute('class',$error_color_arr[$errorcnt])
                    }
                    else
                    {
                        try
                        {
                        $xml02.table.tr[$trcnt].ChildNodes[$tdcnt].SetAttribute('class',$error_color_arr[$errorcnt])
                        }
                        catch{}
                                       
                    }
                }
            }

            if($item02 -like "*replace with*")
            {
                #write-host "....... xml" -ForegroundColor Red
                $replace_index01 = $item02.split("replace with")[$item02.split("replace with").GetUpperBound(0)]
                #write-host "replace index01: " $replace_index01 -ForegroundColor Blue
                #write-host "replacement: " $global:replace_arr01[$replace_index01]
                #$test = $global:replace_arr01[$replace_index01] | ConvertTo-Html -fragment
                [xml]$dummyxml = $global:replace_arr01[$replace_index01] | ConvertTo-Html -fragment
                [xml]$dummyxml = replace_sub_table_xml_recurse([xml]$dummyxml)

                # RECURSE HERE if there is a sub text for replacement
                $xml02.table.tr[$trcnt].ChildNodes[$tdcnt].innertext = ""
                $imported = $xml02.ImportNode($dummyxml.DocumentElement, $true)
                ### prod (sub table in column 1)
                #$xml02.table.tr[$trcnt].ChildNodes[$tdcnt+1].AppendChild($imported) | out-null
                ### test (sub table in last column)
                $xml02.table.tr[$trcnt].ChildNodes[($xml02.table.tr[$trcnt].td.Count)-1].AppendChild($imported) | out-null
            }

        }
    }
    return $xml02
}

function replace_sub_table_xml_recurse($xml01)
{
    for($trcnt=0;$trcnt -lt $xml01.table.tr.count;$trcnt++)
    {
        #write-host "recurse xml" -ForegroundColor Red
        ################################################################
        # change color if certain keywords are found in html cell
        ################################################################
        for($tdcnt=0;$tdcnt -lt $xml01.table.tr[$trcnt].td.Count;$tdcnt++)
        {
            # cannot index element if there is only one
            if($xml01.table.tr[$trcnt].td.Count -eq 1)
            {
                $item03 = $xml01.table.tr[$trcnt].td
            }
            else
            {
                $item03 = $xml01.table.tr[$trcnt].td[$tdcnt]
            }

            ####################
            # color item
            ####################
            for($errorcnt = 0; $errorcnt -lt $error_message_arr.count; $errorcnt++)
            {
                $error_message = $error_message_arr[$errorcnt]
                if($item03 -like "*$error_message*")
                {
                    #write-host "changed color" $error_color_arr[$errorcnt] -ForegroundColor Yellow
                   
                    # cannot index element if there is only one
                    if($xml01.table.tr[$trcnt].td.Count -eq 1)
                    {
                        $xml01.table.tr[$trcnt].ChildNodes.SetAttribute('class',$error_color_arr[$errorcnt])
                    }
                    else
                    {
                        try
                        {
                        $xml01.table.tr[$trcnt].ChildNodes[$tdcnt].SetAttribute('class',$error_color_arr[$errorcnt])
                        }
                        catch{}
                                       
                    }
                }
            }

            if($item03 -like "*replace with*")
            {
                #write-host "recurse xml" -ForegroundColor Red
                #write-host "recurse xml" -ForegroundColor Red
                $replace_index01 = $item03.split("replace with")[$item03.split("replace with").GetUpperBound(0)]
                #write-host "replace index01: " $replace_index01 -ForegroundColor Blue
                #write-host "replacement: " $global:replace_arr01[$replace_index01]
                #$test = $global:replace_arr01[$replace_index01] | ConvertTo-Html -fragment
                [xml]$dummyxml = $global:replace_arr01[$replace_index01] | ConvertTo-Html -fragment
                [xml]$dummyxml = replace_sub_table_xml_recurse([xml]$dummyxml)

                # RECURSE HERE if there is a sub text for replacement
                $xml01.table.tr[$trcnt].ChildNodes[$tdcnt].innertext = ""
                $imported = $xml01.ImportNode($dummyxml.DocumentElement, $true)
                #$xml01.table.tr[$trcnt].ChildNodes[$tdcnt+1].AppendChild($imported) | out-null
                ### test (sub table in last column)
                $xml01.table.tr[$trcnt].ChildNodes[($xml01.table.tr[$trcnt].td.Count)-1].AppendChild($imported) | out-null
            }

        }
    }
    return $xml01
 
}

function papersize01($size01, $orientation01)
{
    #write-host "orientation: " $orientation01 -ForegroundColor Yellow
    $paper01 = New-Object PsObject
    $paper01 | Add-Member NoteProperty -Name orientation -value ''
    $paper01 | Add-Member NoteProperty -Name height -value ''
    $paper01 | Add-Member NoteProperty -Name width -value ''
    $paper01 | Add-Member NoteProperty -Name size -value ''

    If($size01 -eq "ledger" -Or $size01 -eq "11x17" -Or $size01 -eq "tabloid" -Or $size01 -eq "tabloïd")
    #Tabloid / US, B / ANSI B   11 x 17 279 x 432
    {
        $paper01.size = "tabloid"
        If ($orientation01 -eq "portrait")
        {
            $paper01.orientation = "portrait"
            $paper01.height01 = "432"
            $paper01.width01 = "279"
        }
        Else
        {
            $paper01.orientation = "landscape"
            $paper01.height = "279"
            $paper01.width = "432"
        }
    }
    ElseIf($size01 -eq "letter" -Or $size01 -eq "lettre" -Or $size01 -eq "us letter")
    {
        $paper01.size = "us letter"
       
        If ($orientation01 -eq "portrait")
        {
            $paper01.orientation = "portrait"
            $paper01.height = "297"
            $paper01.width = "210"
        }
        Else
        {
           
            $paper01.orientation = "landscape"
            $paper01.height = "210"
            $paper01.width = "297"
        }
    }
    ElseIf($size01 -eq "legal")
    {
        $paper01.size = "legal"
        If ($orientation01 -eq "portrait")
        {
            $paper01.orientation = "portrait"
            $paper01.height = "356"
            $paper01.width = "210"
        }
        Else
        {
            $paper01.orientation = "landscape"
            $paper01.height = "210"
            $paper01.width = "356"
        }
    }
    Else
    {
        $paper01.size = "us letter"
        If( $orientation01 -eq "portrait")
        {
            $paper01.orientation = "portrait"
            $paper01.height = "297"
            $paper01.width = "210"
        }
        Else
        {
            $paper01.orientation = "landscape"
            $paper01.height = "210"
            $paper01.width = "297"
        }
    }
    return $paper01
}

function web_report($wrp02)
{
    ########################
    # web report
    ########################

    $paper01 = papersize01 "letter" "portrait"

    $paper01size = $paper01.size
    $paper01orientation = $paper01.orientation
    $paper01height = $paper01.height + "mm"
    $paper01width = $paper01.width + "mm"

$pagesetup01 = @"
@page
{
margin: 0.2in 0.2in 0.2in 0in;
size:$paper01size $paper01orientation;
}
HTML body
{
height:$paper01height;width:$paper01width;margin: 0.2in 0in 0in 0.2in;
}
"@

    $leftsidewidth01 = "15"
    $rightsidewidth01 = "15"
    [string]$middlewidth01 = 100 - $leftsidewidth01 - $rightsidewidth01
    ######################
    # thead
    ######################
#$thead01 = @"
#<THEAD><TR><th>
#<table width=""100%"" BORDERCOLOR=""black"" border=1 CELLSPACING=1 cellpadding=2 style='border-collapse:collapse;border:none;$wrp02.header_title_line_style01'>"
#<tr>
#<td width=""" + $leftsidewidth01 + "%""><img style='"
#"@

    $thead01 = ""
    $thead01 = $thead01 + "<THEAD>" + "`n"
    $thead01 = $thead01 + "<TR id=""t01""><th id=""t01"">" + "`n"
    #=== table inside header only
    #$thead01 = $thead01 + "<table id=""tr02"" width=""100%"" BORDERCOLOR=""black"" border=1 CELLSPACING=1 cellpadding=2 style='border-collapse:collapse;border:none;" + $wrp02.header_title_line_style01 + "'>" + "`n"
    $thead01 = $thead01 + "<table id=""tr02"" width=""100%"" CELLSPACING=1 cellpadding=2 style='" + $wrp02.header_title_line_style01 + "'>" + "`n"
    $thead01 = $thead01 + "<tr>" + "`n"
    #=== left side image
    $thead01 = $thead01 + "<td width=""" + $leftsidewidth01 + "%""><img style='"
    If($wrp02.header_left_image_percenty01 -gt 0)
    {
        $thead01 = $thead01 + "height: " + $wrp02.header_left_image_percenty01 + "%;"
    }
    If($wrp02.header_left_image_percentx01 -gt 0)
    {
        $thead01 = $thead01 + "width: " + $wrp02.header_left_image_percentx01 + "%;"
    }
    $thead01 = $thead01 + "object-fit: contain' "
    $thead01 = $thead01 + "src=""" + $wrp02.header_left_image_path01 + """ ></td>" + "`n"
    #=== title
    $thead01 = $thead01 + "<td width=""" + $middlewidth01 + "%"">"
    $thead01 = $thead01 + "<font size=""5"">" + $wrp02.header_title_line01 + "</font><br>"
    #=== second title
    $thead01 = $thead01 + "<font size=""3"">" + $wrp02.header_title_line02 + "</font></td>" + "`n"
    #=== right side image
    $thead01 = $thead01 + "<td width=""" + $rightsidewidth01 + "%""><img style='"
    If($wrp02.header_right_image_percenty01 -gt 0)
    {
        $thead01 = $thead01 + "height: " + $wrp02.header_right_image_percenty01 + "%;"
    }

    If($wrp02.header_right_image_percentx01 -gt 0)
    {
        $thead01 = $thead01 + "width: " + $wrp02.header_right_image_percentx01 + "%;"
    }
    $thead01 = $thead01 + "Object-fit: contain' "
    $thead01 = $thead01 + "src=""" + $wrp02.header_right_image_path01 + """></td>" + "`n"
    $thead01 = $thead01 + "</tr>"
    $thead01 = $thead01 + "</table>" + "`n" #=== header
    $thead01 = $thead01 + "</th></TR>" + "`n"
    $thead01 = $thead01 + "</THEAD>" + "`n"
    ############################
    # tfoot
    ############################
    $tfoot01 = ""
    $tfoot01 = $tfoot01 + "<TFOOT style='" + $wrp02.footer_style01 + "'>`n"
    $tfoot01 = $tfoot01 + "<TR id=""t01""><th id= ""t01"">"
    $tfoot01 = $tfoot01 + "<table id=""t01"" width=""100%"" BORDERCOLOR=""black"" border=1 CELLSPACING=1 cellpadding=2 style='border-collapse:collapse;border:none;" + $wrp02.footer_table_style01 + "'>" + "`n"
    #$tfoot01 = $tfoot01 + "<tr style = 'page-break-inside: avoid;'>"
    $tfoot01 = $tfoot01 + "<td width=""25%"" style='text-align: center;'>" + $wrp02.footer_left01 + "</td>" + "`n"
    $tfoot01 = $tfoot01 + "<td width=""50%"" style='text-align: center;'>" + $wrp02.footer_middle01 + "</td>" + "`n"
    $tfoot01 = $tfoot01 + "<td width=""25%"" style='text-align: center;'>" + $wrp02.footer_right01 + "</td>" + "`n"
    $tfoot01 = $tfoot01 + "</tr>"
    $tfoot01 = $tfoot01 + "</table>"
    $tfoot01 = $tfoot01 + "</th></TR>"
    $tfoot01 = $tfoot01 + "</TFOOT>" + "`n"

    ############################
    # html header
    ############################

# not used anymore
#$header = @"
#<style>$pagesetup01
#BODY{background-color:white;}
#TABLE{page-break-after: always;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
#TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: LightBlue}
#TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: white}
#.green{background-color:#d5f2d5}
#.blue{background-color:#e0eaf1}
#.red{background-color:#ffd7de}
#.yellow{background-color:#ffff00}
#.orange{background-color:#ffa500}
#</style>
#"@

#tr02{border-bottom:1px solid black;border-right:1px solid black;background-color: LightBlue;border-width: 1px;padding: 5px;border-style: solid;border-color: black;}
#td02{border-bottom:1px solid black;border-right:1px solid black;background-color: LightBlue;border-width: 1px;padding: 5px;border-style: solid;border-color: black;}

#tr02{border-top: 1px solid #FFFFFF;padding: 5px;border-style: solid;border-color: black;background-color: LightBlue;border-width: 1px;border-spacing: -1px;}
#td02{border-top: 1px solid #FFFFFF;padding: 5px;border-style: solid;border-color: black;background-color: LightBlue;border-width: 1px;border-spacing: -1px;}


$style01 = @"
$pagesetup01
#t01{width: 100%; border-collapse:collapse;border:none;background-color: White}
BODY{background-color:white;}
TABLE{border-spacing:0;page-break-after: always;border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: LightBlue}
TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: White}
TR{page-break-inside: avoid;}
#tr02{padding: 5px;background-color: white;border-width: 1px;border-style: solid;border-color: black;position: relative;}
.green{background-color:#d5f2d5}
.blue{background-color:#e0eaf1}
.red{background-color:#ffd7de}
.yellow{background-color:#ffff00}
.orange{background-color:#ffa500}
"@

#<table id=""t01"" width=""100%"" BORDERCOLOR=""black"" border=0 CELLSPACING=1 cellpadding=2>
#$style01 = ""

$body = @"
<style>
$style01
</style>
<table id="t01" CELLSPACING=1 cellpadding=2>
$thead01
<tbody id="DATA">
<tr id="t01"><td id="t01">
$($wrp02.data)
</td></tr>
</tbody>
$tfoot01
</table>
"@
    return $body
}

######################################################################################################################################################
# main
######################################################################################################################################################

########################################################################
# elevation Self-elevate the script (pop-up will ask for elevation)
########################################################################
if (-Not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] 'Administrator'))
{
    if ([int](Get-CimInstance -Class Win32_OperatingSystem | Select-Object -ExpandProperty BuildNumber) -ge 6000)
    {
        $CommandLine = "-File `"" + $MyInvocation.MyCommand.Path + "`" " + $MyInvocation.UnboundArguments
        Start-Process -FilePath PowerShell.exe -Verb Runas -ArgumentList $CommandLine
        Exit
    }
}

$level0_arr = @()
$level1_arr = @()
$level2_arr = @()

###############
# level 0
###############
$level0_obj = New-Object PsObject
$level0_obj | Add-Member NoteProperty -Name id -value '1'
$level0_obj | Add-Member NoteProperty -Name test -value 'test1'

$level0_arr += $level0_obj.PSObject.Copy()

$level0_obj = New-Object PsObject
$level0_obj | Add-Member NoteProperty -Name id -value '2'
$level0_obj | Add-Member NoteProperty -Name test -value 'test2'

$level0_arr += $level0_obj.PSObject.Copy()

    ############# level 1
    $level1_obj = New-Object PsObject
    $level1_obj | Add-Member NoteProperty -Name id -value '3'
    $level1_obj | Add-Member NoteProperty -Name test -value 'sub 0 test3'

    $level1_arr += $level1_obj.PSObject.Copy()

    $level1_obj = New-Object PsObject
    $level1_obj | Add-Member NoteProperty -Name id -value '4'
    $level1_obj | Add-Member NoteProperty -Name test -value 'sub 0 test4'

    $level1_arr += $level1_obj.PSObject.Copy()

        ############# level 2
        $level2_obj = New-Object PsObject
        $level2_obj | Add-Member NoteProperty -Name id -value '5'
        $level2_obj | Add-Member NoteProperty -Name test -value 'sub 1 test5'

        $level2_arr += $level2_obj.PSObject.Copy()

        $level2_obj = New-Object PsObject
        $level2_obj | Add-Member NoteProperty -Name id -value '6'
        $level2_obj | Add-Member NoteProperty -Name test -value 'sub 1 test6'

        $level2_arr += $level2_obj.PSObject.Copy()

        $level1_arr += ,$level2_arr

#############
$level0_arr +=,$level1_arr

######################################
# pssession parameters
######################################
[string]$dummy = $null
$jobtimeout01 = 3*60 #( 3 minutes)
$OpenTimeoutMSec01 = 5000
$OperationTimeoutMSec01 = 180000 # 3 minutes
$PSSessionOption01 = New-PSSessionOption -OpenTimeout $OpenTimeoutMSec01 -OperationTimeout $OperationTimeoutMSec01

$prod01 = 1
if($prod01 -eq 1)
{
    ####################################
    # get windows key from bios
    ####################################
    #$msg01 = "sub array value...: " + $level0_arr[2][2][1][0].test #= id 6 sub1 test 6
    #$msg01 = "sub array value...: " + $level0_arr[2][0][0] #= id 6 sub1 test 6
    #Format-Custom -InputObject $level0_arr
    #write-host $msg01 -ForegroundColor Magenta
   
   
    $level0_arr = @()
    $id01 = 0
   
    if ($domain01 -eq "workgroup")
    {
        # not in a domain, use a local login for pssession
        $Username = "$vm_name01\adminlocal"
        $Password = 'Lamaisondelapizza'
        try
        {
            $SecureString = ConvertTo-SecureString -AsPlainText $Password -Force
        }catch{}

        # LOCAL login and password
        $MySecureCreds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Username,$SecureString

        ###################
        $computers01 = $computers_local01
    }   
   
    foreach($computer01 in $computers01)
    {
        ########################################
        # open pssession to ask each computer
        ########################################
        $error01.number = 0
        try
        {
            if($domain01 -eq "workgroup")
            {
                $s2 = New-PSSession -ComputerName $computer01 -SessionOption $PSSessionOption01 -Credential $MySecureCreds -erroraction stop
            }
            else
            {
                write-host "Pssession openning: " $computer01 -ForegroundColor Magenta
                $s2 = New-PSSession -ComputerName $computer01 -SessionOption $PSSessionOption01 -erroraction stop
            }
        }
        catch
        {
            #$error01.number = 1
            #write-host "ERROR Pssession openning: " $computer01 -ForegroundColor red
        }
       
        if($s2 -ne $null)
        {
            #######################################
            # oscaption (operation system name)
            #######################################
            $remote_command01 = "try {(Get-WMIObject Win32_OperatingSystem -erroraction stop).caption} catch {throw `$_}"
            $remote_command_block01 = [scriptblock]::Create($remote_command01)

            $job_caption = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
            $results01 = $job_caption | Wait-Job -timeout $jobtimeout01 | Receive-Job
            $job_caption | remove-job
               
            $oscaption = $results01


            $level0_obj = New-Object PsObject
            $level0_obj | Add-Member NoteProperty -Name id -value $id01
            $level0_obj | Add-Member NoteProperty -Name computer_netbios_name -value $computer01
            $level0_obj | Add-Member NoteProperty -Name type -value $oscaption
       
            $level0_arr += $level0_obj.PSObject.Copy()

                #################################
                # licence windows from bios
                #################################
                $level1_obj = New-Object PsObject
                $level1_obj | Add-Member NoteProperty -Name id -value '1'
                $level1_obj | Add-Member NoteProperty -Name data -value "licences"
                $level1_arr = @()
                $level1_arr += $level1_obj.PSObject.Copy()

                    #################################
                    # licence windows from bios
                    #################################
                    $level2_obj = New-Object PsObject
                    $level2_obj | Add-Member NoteProperty -Name id -value 'windows from bios'
               
                    ################################################
                    # bios windows code (or other codes in bios)
                    ################################################
               
                    $remote_command01 = "try {(Get-WmiObject -query 'select * from SoftwareLicensingService').OA3xOriginalProductKey} catch {throw `$_}"
                    $remote_command_block01 = [scriptblock]::Create($remote_command01)

                    $job01 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
                    $results01 = $job01 | Wait-Job -timeout $jobtimeout01 | Receive-Job
                    $job01 | remove-job
               
                    $bios_keys = $results01
                                       
                    $level2_obj | Add-Member NoteProperty -Name data -value $bios_keys
                    $level2_arr = @()
                    $level2_arr += $level2_obj.PSObject.Copy()

                    ####################################
                    # windows key from registry
                    ####################################
                    $level2_obj = New-Object PsObject
                    $level2_obj | Add-Member NoteProperty -Name id -value 'windows from digitalid'

                    $path01 = "HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion"
                    #$a = Get-RemoteRegistryInformation -Key $reg_ProductKey -AsObject
                   
                    $remote_command01 = "try {get-ItemProperty -Path `"$path01`" -erroraction stop} catch {throw `$_}"
                    $remote_command_block01 = [scriptblock]::Create($remote_command01)

                    $job01 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
                    $results01 = $job01 | Wait-Job -timeout $jobtimeout01 | Receive-Job
                    $job01 | remove-job
                    $a= $null
                    $a = $results01
                                       
                    #$a = get-ItemProperty -Path $path01 -erroraction stop
                    $digital_id_key =""
                    $digital_id_key = ConvertTo-ProductKey $a.DigitalProductId
                    #ConvertTo-ProductKey $a.DigitalProductId4 -x64

                    $level2_obj | Add-Member NoteProperty -Name data -value $digital_id_key
                    $level2_arr += $level2_obj.PSObject.Copy()

                    ###################################
                    # office find ospp.vbs to find key
                    ###################################
                    # 15 = office 2016
                    [string[]]$office_all_ospp01 = "C:\Program Files\Microsoft Office\Office15\OSPP.VBS"
                    [string[]]$office_all_ospp01+= "C:\Program Files (x86)\Microsoft Office\Office15\OSPP.VBS"
                    [string[]]$office_all_ospp01+= "C:\Program Files\Microsoft Office\Office16\OSPP.VBS"
                    [string[]]$office_all_ospp01+= "C:\Program Files (x86)\Microsoft Office\Office16\OSPP.VBS"
                   
                    $office_ospp_found01 = -1
                    for($osppcnt = 0; $osppcnt -lt $office_all_ospp01.count; $osppcnt++)
                    {
                        # check if ospp.vbs exist
                        $office_ospp01 = $office_all_ospp01[$osppcnt]

                        $msg01 = "Checking if path exist: " + $office_ospp01
                        write-host $msg01
                       
                        $remote_command01 = "try {Test-Path `"$office_ospp01`"} catch {throw `$_}"
                        $remote_command_block01 = [scriptblock]::Create($remote_command01)

                        $job01 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
                        $results01 = $job01 | Wait-Job -timeout $jobtimeout01 | Receive-Job
                        #write-host "job status: " $job01.StatusMessage -ForegroundColor Yellow
                        $job01 | remove-job

                        #write-host "result...: " $results01

                        if ($results01 -eq $true)
                        {
                            $office_ospp_found01 = $osppcnt
                        }
                       
                    }
                    $office_ospp02 = $office_all_ospp01[$office_ospp_found01]
                   
                    if($office_ospp_found01 -ne -1)
                    {
                        ##################################
                        # office key(s) last five digits
                        ##################################
                        #$productKey=cscript "C:\Program Files (x86)\Microsoft Office\Office15\OSPP.VBS" /dstatus | Select-String -Pattern $regex -AllMatches | % { $_.Matches } | % {$_.Value}
                        #$productKey=cscript $office_ospp01 /dstatus
                       
                        #c:\windows\system32\cscript.exe "C:\Program Files\Microsoft Office\Office16\OSPP.VBS" /dstatus
                        $remote_command01 = "try {c:\windows\system32\cscript.exe `"$office_ospp02`" /dstatus} catch {throw `$_}"
                        $remote_command_block01 = [scriptblock]::Create($remote_command01)

                        $job01 = Invoke-Command -asjob -Session $s2 -ScriptBlock $remote_command_block01 -ErrorAction stop | Get-Job
                        $results01 = ""
                        $results01 = $job01 | Wait-Job -timeout $jobtimeout01 | Receive-Job
                        #write-host "job status: " $job01.StatusMessage -ForegroundColor Yellow
                        $job01 | remove-job
                   
                        #$job01
                        #break all
                   
                        $alllines = $results01.split("`n")
                        $i=0
                        [string[]]$productid = @()
                        $skuid01 = @()
                        $licensename01 = @()
                        $keys01 = @()
                        foreach($line01 in $alllines)
                        {
                            #write-host $i " " $line01
                            #$i++
                            if($line01 -like "*PRODUCT ID*")
                            {
                                $line01
                                $productid += $line01.Split(" ")[$line01.Split(" ").GetUpperBound(0)]
                            }
                            if($line01 -like "*sku id*")
                            {
                                $line01
                                $skuid01 += $line01.Split(" ")[$line01.Split(" ").GetUpperBound(0)]
                            }
                            $tag01 = "LICENSE NAME: "
                            if($line01 -like "*$tag01*")
                            {
                                $line01
                                $licensename01 += $line01.replace($tag01, "")
                            }
                   
                            if($line01 -like "*Last 5 characters of installed product key*")
                            {
                                $line01
                                $keys01 += $line01.Split(" ")[$line01.Split(" ").GetUpperBound(0)]
                            }

                        }

                        #$productid
                        #$skuid01
                        #$licensename01
                        #$key01

                        #$regex='\b([A-Z1-9]{5}$)\b'
                        #$office_key = $results01 | Select-String -Pattern $regex -AllMatches | % { $_.Matches } | % {$_.Value}

                        $level22_arr = @()
                        for($liccnt01=0; $liccnt01 -lt $keys01.count; $liccnt01++)
                        {
                            $level22_obj = New-Object PsObject
                            $level22_obj | Add-Member NoteProperty -Name id -value 'office'
                            $level22_obj | Add-Member NoteProperty -Name productid -value $productid[$liccnt01]
                            $level22_obj | Add-Member NoteProperty -Name skuid -value $skuid01[$liccnt01]
                            $level22_obj | Add-Member NoteProperty -Name licensename -value $licensename01[$liccnt01]
                            $level22_obj | Add-Member NoteProperty -Name data -value $keys01[$liccnt01]
                            $level22_arr += $level22_obj.PSObject.Copy()
                        }
                    }
                    else
                    {
                        $msg01 = "ERROR did not find ospp.vbs"
                        write-host $msg01 -ForegroundColor Red
                       
                        $level22_arr = @()
                        $level22_obj = New-Object PsObject
                        $level22_obj | Add-Member NoteProperty -Name id -value 'office'
                        $level22_obj | Add-Member NoteProperty -Name ERROR -value $msg01
                        $level22_arr += $level22_obj.PSObject.Copy()
                    }       
            #$TEST = New-Object PsObject
            #$test | Add-Member NoteProperty -Name id -value '1111'
            #$test
            ######################################
            # 1 line for each type of licences
            ######################################
            $level1_arr += ,$level2_arr
            $level1_arr += ,$level22_arr
            $level0_arr += ,$level1_arr
            #$level0_arr+="test"

            #$level0_arr[1][1].data = "test"
            #$level0_arr[1].data = "test" #$level1_obj #.PSObject.Copy()

            #$msg01 = "sub array value...: " + $level0_arr[1].data
            #write-host $msg01 -ForegroundColor Magenta

            #$level0_arr[1][1] = "test"

            #$level0_arr =,$level1_arr
        } # pssession not null
        else
        {
            $msg01 = "ERROR could not open a pssession on " + $computer01 + ". Try powershell enable-psremoting on the machine"
            if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}
            write-host $msg01 -ForegroundColor Red

            $level0_obj = New-Object PsObject
            $level0_obj | Add-Member NoteProperty -Name id -value $id01
            $level0_obj | Add-Member NoteProperty -Name computer_netbios_name -value $computer01
            $level0_obj | Add-Member NoteProperty -Name type -value $msg01
       
            $level0_arr += $level0_obj.PSObject.Copy()
           
        }
        $id01++
        try{$s2.close}catch{}
        $s2 = $null
    } # for each computer01 in computers01

    #$level0_arr += ,$level1_obj

    #$level0_arr = get-wmiobject -class win32_logicaldisk | select deviceid, freespace, size, volumename
} # prod01
##############################
# get installed programs
##############################
#$properties = @('DisplayName', 'DisplayVersion', 'Publisher', 'InstallDate', "EstimatedSize") #, 'DisplayVersion', 'Publisher', 'InstallDate'
#$level0_arr = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object $properties

#write-host "before" -ForegroundColor Yellow

####################################
# test content of jagged array
####################################
#$level0_arr[2][2][1] = id 6 sub1 test 6

###################################################################
# replace sub object with texts to be able to generate html table
###################################################################
$file01 = report_object_replacements01($level0_arr)

$msg01 = "Number of items to replace: " + $global:item_to_replace_index01 + " count: " + $global:replace_arr01.count
#write-host $msg01
if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}

# inspect elements
for($elecnt02 = 0; $elecnt02 -lt $global:replace_arr01.count; $elecnt02++)
{
    $msg01 = "Ele: " + $elecnt02 + " " + $global:replace_arr01[$elecnt02]
    #write-host $msg01
    if ($logall -eq 1) {try{(Get-Date -Format "yyyy-MM-dd HH:mm:ss") + " " + $msg01 | Out-File $logfile -append}catch{}}
}

########################################
# main html table
########################################
[xml]$level0_arr_xml01 = $level0_arr | ConvertTo-Html -fragment

#############################################
# put back each object in a html sub table
#############################################
$level0_arr_xml01  = replace_sub_table_xml($level0_arr_xml01)

$body = ""
$htmlfile02 = ""

####################################################
# page setup for web print thead tfoot with logo
####################################################

$wrp02 = New-Object PsObject # stand for web report parameters

$wrp02 | Add-Member NoteProperty -Name header_title_line01 -value 'Audit microsoft licenses'
$wrp02 | Add-Member NoteProperty -Name header_title_line02 -value 'Rapport'

$wrp02 | Add-Member NoteProperty -Name paper01 -value 'letter'
$wrp02 | Add-Member NoteProperty -Name orientation01 -value 'portrait'

$wrp02 | Add-Member NoteProperty -Name header_title_line_style01 -value ''

$wrp02 | Add-Member NoteProperty -Name header_left_image_percentx01 -value '70'
$wrp02 | Add-Member NoteProperty -Name header_left_image_percenty01 -value '0'
#$wrp02 | Add-Member NoteProperty -Name header_left_image_path01 -value 'https://www.tremblaycie.com/wp-content/uploads/2017/11/cropped-tremblay-cie.png'
$wrp02 | Add-Member NoteProperty -Name header_left_image_path01 -value 'https://devicom365.sharepoint.com/sites/TableaudeBordDevicom/_api/GroupService/GetGroupImage?id=%279d43061d-8e7c-43dd-a94a-f9bea7433034%27&hash=637193715108549624'

$wrp02 | Add-Member NoteProperty -Name header_right_image_percentx01 -value '70'
$wrp02 | Add-Member NoteProperty -Name header_right_image_percenty01 -value '0'
$wrp02 | Add-Member NoteProperty -Name header_right_image_path01 -value 'https://devicom365.sharepoint.com/sites/TableaudeBordDevicom/_api/GroupService/GetGroupImage?id=%279d43061d-8e7c-43dd-a94a-f9bea7433034%27&hash=637193715108549624'

$wrp02 | Add-Member NoteProperty -Name footer_style01 -value ''
$wrp02 | Add-Member NoteProperty -Name footer_table_style01 -value ''
$wrp02 | Add-Member NoteProperty -Name footer_left01 -value ''
$wrp02 | Add-Member NoteProperty -Name footer_middle01 -value ''
$date01 = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$wrp02 | Add-Member NoteProperty -Name footer_right01 -value $date01

# object containing jagged objects
$wrp02 | Add-Member NoteProperty -Name data -value $level0_arr_xml01.innerxml

$body = web_report($wrp02)

$htmlfile02 = ConvertTo-Html -Body $body

$htmlfile02 | Out-File $file01
& $file01

# futur search jquery
#var allRows = $("tr");
#$("input#search").on("keydown keyup", function() {
#  allRows.hide();
#  $("tr:contains('" + $(this).val() + "')").show();
#});