3 mars 2018

Résoudre un bug des marques-pages de Firefox qui se multiplient

D'avance désolé pour les impressions d'écrans en 4K.

Peu après avoir activé la synchronisation de mon très vieux profile Firefox (que je me copie de PC en PC) vers mon "Compte Firefox", je me suis retrouvé avec un bug gênant...
- une partie de mes favoris se sont retrouvés dupliqués des milliers de fois
- des lignes "étiquettes récentes" en dizaines d'exemplaires
- parfois impossible d'ajouter un favoris par l'étoile dans la barre d'adresse (et elle pouvait même être "cochée" alors que l'URL n'avait jamais été ajoutée...)
- impossible de déplacer un marque-page d'un dossier à un autre depuis le gestionnaire de marque-pages
- impossible d'exporter les marque-pages ni en json ni en html (la fonction demande le chemin de destination mais le fichier n'est jamais créé)



Ma solution va consister à nettoyer le fichier local des favoris de Firefox pour repérer et supprimer les marque-pages qui se sont démultipliés.
Pour cela, on aura besoin de SQLite Brower (gratuit) dispo ici : https://github.com/sqlitebrowser/sqlitebrowser
Ouvrir le fichier c:\users\<user>\AppData\Roaming\Mozilla\Firefox\Profiles\<votre profile ID>\places.sqlite (par sécurité, avant de modifier le fichier, j'en ai fais une copie)



Nous allons devoir faire quelques requêtes SQL pour nettoyer, mais avant vous pouvez consulter l'onglet Browse Data et choisir la table moz_bookmarks et voir en bas le nombre d'entrées (j'en avais plus de 70 000).



Les requêtes sont à exécuter dans l'onglet Execute SQL.

La première requête consiste à lister les favoris qui existent en plusieurs exemplaires :
select count(fk) as c, fk from moz_bookmarks group by fk order by c desc



Ceci va afficher une liste avec deux colonnes, la première est le nombre d’occurrences et la seconde les ID des favoris.

Je sélectionne (en maintenant Ctrl enfoncé) toutes les cellules des ID dont l'occurrence > 50 et Ctrl+C (mes favoris bugués sont en 2600 exemplaires dans mon cas). Je colle dans Notepad++ et je fais un remplacement "\r\n" en , pour obtenir une liste d'ID séparées par virgule.



La requête suivante consiste à lister les URL des favoris que nous allons supprimer. Je les copies et j'aurai à les refaire manuellement s'ils m'intéressent toujours.
select distinct * from moz_places where id in(40,46,47,65,99,110,111,112,113,115,116,118,119,120,2516,107,108,109,114,117)
Pareil, on peut copier la colonne en résultat pour une sauvegarde.

Maintenant que notre sauvegarde est faite, la dernière requête va permettre de supprimer tous ces marques pages d'un coup :
delete from moz_bookmarks where fk in(40,46,47,65,99,110,111,112,113,115,116,118,119,120,2516,107,108,109,114,117)

Le problème n'est pas terminé car on se retrouve avec des dossiers de marque-pages qui n'ont pas été supprimés (car ils n'ont pas d'URL...). Pour cela, on va lister tous les dossiers qui possèdent le plus d'éléments (et on devrait trouver le dossier qui contient ces milliers de dossier vide) :
select count(parent) as c, parent from moz_bookmarks group by parent order by c desc

J'obtiens 2 résultats dont l'occurrence est supérieur à 1000 (ça veut dire que ces dossiers possèdent plus de 1000 enfants directs). On va supprimer tous ces enfants de type dossier (cela ne supprimera pas les favoris de ce dossier).

Une première requête pour voir ce qui va être supprimé :
select title from moz_bookmarks where parent in(11279,1529) and type=2
Une dernière requête pour supprimer pour de bon (type=2 permet de ne cibler que les dossiers).
delete from moz_bookmarks where parent in(11279,1529) and type=2



On retourne dans l'onglet Browse Data et on constate que le nombre d'entrées à bien diminué (je suis passé de 70 000 à 14 000). On valide l'écriture du fichier.



On lance Firefox pour voir. Nickel, tout fonctionne à nouveau.

9 sept. 2015

[Howto] Mémoriser le mot de passe Exchange dans Outlook une bonne fois pour toutes !

Dans d'obscures conditions, Outlook 2007, 2010 ou 2013 peuvent demander sans cesse le mot de passe du compte Exchange. Parfois juste après l'avoir validé, il réitère la demande.

Soit les conditions et symptômes suivants :
- Le compte de messagerie est un compte Exchange
- Le problème persiste en RPC ou HTTPS
- Avec OWA il n'y a pas de problème
- Même en cochant Mémoriser le mot de passe, le problème persiste
- Pour Outlook 2013, même en supprimant l'entrée Outlook15 dans le gestionnaire d'identification de Windows 7 ne corrige pas le problème

Outlook affiche une clé en bas à gauche Mot de passe requis.


Pourtant, si on clique sur Annuler, il ne pose plus la question pour un moment (à priori jusqu'au prochain cycle d'Envoyer/recevoir défini dans Définir les groupes d'envoi/réception). Et tout fonctionne correctement : l'envoi, la réception, les tâches, les calendriers, le carnet...

Solution :

Il suffit de créer la clé (s'il elle n'existe pas déjà de registre suivante :
HKEY_CURRENT_USER\Software\Microsoft\Office\XX.0\Outlook\RPC (où XX correspond à votre version de Microsoft Office) et de créer un DWORD UseWindowsUserCredentials avec la valeur 1.

État du registre après application de la solution


Relancez Outlook.



[snippet] Comment s'approprier rapidement des profils itinérants ?

Vous voulez vous approprier tous les profils itinérants Windows XP ? Mais si vous faites ça, les utilisateurs n'auront plus accès à leur profils itinérant et obtiendrons une erreur à l'ouverture de session.
Le profil itinérant possède une ACL NTFS qui indique que l'utilisateur du profil est le propriétaire du répertoire et des fichiers. L'administrateur ne peut pas accéder à ces fichiers, car il ne figure pas dans l'ACL par défaut. Une GPO existe pour rajouter le groupe Administrateurs dans l'ACL à la création du répertoire, mais admettons que cette GPO ne s'applique pas ou a été créée après les répertoires.
Le but du jeu est d'ajouter le groupe Administrateurs dans l'ACL de chaque répertoire. Or, pour modifier une ACL, il faut pouvoir y accéder, ce qui n'est pas le cas... La seule façon est de s'approprier le répertoire (ce qui va remplacer l'ACL existante par une ACL vierge, avec pour seule entrée le nouveau propriétaire). En faisant cela, les utilisateurs n'auront donc plus le droit d'accéder à leur propre profil. Il faut rajouter l'utilisateur dans l'ACL en contrôle total, et ce, récursivement pour les fichiers existants.

Attention : pour que Windows autorise de charger un profil itinérant, même si l'utilisateur a un contrôle total sur tous ses fichiers, il faut que le propriétaire du répertoire soit : soit le compte utilisateur soit le groupe Administrateurs. S'il s'agit d'un autre compte, même le compte Administrateur, le chargement échouera.

Voici le script powershell :

# répertoire à scanner
$dir = "D:\profils"

$profils = Get-ChildItem -Path $dir -Force -ea 0

$profils | ForEach-Object {
    $rep = $_
    if($rep.Mode.startsWith('d')) { # on ignore les fichiers
        $user = $_.Fullname.Split('\')[2]
        $owner = $acl = $null
        $acl = $rep.GetAccessControl()
        $owner = $acl.Owner
        if($owner -ne 'BUILTIN\Administrateurs' -and $owner -ne 'BUILTIN\Administrators') { # le propriétaire n'est pas déjà administrateur, on ne fait rien
            echo "$user MAUVAIS OWNER!"
        }
        else {
            $found =  $false
            $acl.Access|Where-Object{$_.IdentityReference.ToString() -ieq $user}|ForEach-Object{$found=$true} # on vérifie si le répertoire possède déjà l'ACL...
            if($found) {
                Write-Host -fore gray "$user have rights ($owner)"
            }
            else {
                write-host -fore blue "Il faut ajouter DOMAINE\$user."
                $NTuser = New-Object System.Security.Principal.NTAccount("DOMAINE\$user")
                $access = [System.Security.AccessControl.FileSystemRights]::FullControl
                $heritage = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
                $heritagefile = [System.Security.AccessControl.InheritanceFlags]::None
                $propa = [System.Security.AccessControl.PropagationFlags]::None
                $allow = [System.Security.AccessControl.AccessControlType]::Allow
                $ace = New-Object System.Security.AccessControl.FileSystemAccessRule("DOMAINE\$user",$access,$heritage,$propa,$allow) # on cré une ACE pour répertoire
                $acefile = New-Object System.Security.AccessControl.FileSystemAccessRule("DOMAINE\$user",$access,$heritagefile,$propa,$allow) # on cré une ACE pour les fichiers
                $acl.AddAccessRule($ace)
                if(!$?) {
                    echo "Impossible de créer l'ACL"
                }
                else { # l'ACL est OK, on l'applique
                    $rep.SetAccessControl($acl)
                    if($?) { # on répète récursivement aux fichiers et répertoires déjà existants
                        echo "Appropriation récursive de $($rep.Fullname)."
                        $aclfile = $null
                        Get-ChildItem -Path $rep.FullName -Force -Recurse | ForEach-Object {
                            if($_.Mode.StartsWith('d')) { # répertoire...
                                $_.SetAccessControl($acl)
                            }
                            else { # fichier...
                                $aclfile = $_.GetAccessControl() # doit recréer l'ACL à chaque fois pour les fichiers
                                $aclfile.AddAccessRule($acefile)
                                $_.SetAccessControl($aclfile)
                            }
                        }
                        Write-Host -fore green "`r`n$user : OK"
                    }
                    else {
                        Write-Host -fore red "`r`n$user : ERR"
                    }
                }
            }
        }
    }
}

Il est utile pour l'administrateur d'avoir accès au contenu des profils pour vérifier les abus de stockage de fichiers (notamment car le contenu du profil est "streamé" à l'ouverture de la session, contrairement au contenu d'un lecteur réseau ou d'une redirection de Mes documents).

7 sept. 2015

[snippet] Méthode d'extension C# ToTuple

J'ai eu besoin d'exporter sous la forme d'un tableau d'objet certaines colonnes résultantes d'une expression LINQ.
Mon objectif était de sérialiser le résultat en JSON avec JavascriptSerializer avec un tableau non-associatif (pas de paire clé-valeur).

public static object[] ToTuple<T, U, V>(this 
IEnumerable<T> queryable, Func<T, U> t1)
        {
            List<object[]> tab = new List<object[]>();
            foreach (var e in queryable)
                tab.Add(new object[] { t1.Invoke(e) });
            return tab.ToArray();
        }  
 
//...
 
public static object[] ToTuple<T, U, V, W, X, Y, Z>(this 
IEnumerable<T> queryable, Func<T, U> t1, Func<T, V> 
t2, Func<T, W> t3, Func<T, X> t4, Func<T, Y> t5, 
Func<T, Z> t6)
        {
            List<object[]> tab = new List<object[]>();
            foreach (var e in queryable)
                tab.Add(new object[] { t1.Invoke(e), t2.Invoke(e), t3.Invoke(e), t4.Invoke(e), t5.Invoke(e), t6.Invoke(e) });
            return tab.ToArray();
        } 

Ces méthodes sont à ajouter dans des classes statiques pour être utilisables.
Note : on pourrait retourner un Tuple<U,V...>[] à la place de object[], mais la sérialisation serait associative avec les clés Item1, Item2...

16 août 2014

[astuce] La commde systeminfo

Windows XP, Vista, Seven, 2003 Server (...) intègrent cette commande pourtant bien utile. Il s'agit d'une commande méconnue qui permet d'obtenir des informations intéressantes sans être administrateur, comme :
- Le nom de la machine
- Version du système d'exploitation, son fabriquant et son mode d'exécution
- L'utilisateur enregistré ainsi que le ProductID
- Date d'installation de Windows
- Date de démarrage de Windows
- Modèle et fabriquant de la carte mère, information sur le microprocesseur et le BIOS
- Quelques répertoires systèmes et volume de boot
- Quelques paramètres (langue, fuseau horaire, taille du swap...)
- Domaine et contrôleur de domaine (le cas échéant) ou le groupe de travail, ainsi que le nom d'utilisateur
- Les correctifs Microsoft installés
- Les cartes réseaux installées

Résultat de la commande systeminfo

Les commutateurs de la commande permettent de récupérer ces informations sur un poste distant (/S <ip|nom> [/u <[domaine\]login>] [/p <password>]) et également d'exporter les résultats dans un tableau ou en CSV.